[PATCH] 2.6.19 m68k Atari: SCC serial support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

my SCC serial driver - this one works less well in 2.6 than it did in 2.4,
most likely due to the way flip buffer push is handled now - i.e. no
immediate push of received characters to the line disc. if the handler
runs in interrupt context, and the bottom half for pushing is run as
delayed task. 9 out of 10 ping packets end up in the bit bucket this way.
I haven't figured out how to prevent overruns yet, and getting this right
will require a bit more testing.

Anyway, the basics are working, and maybe someone can figure out a better
way to push characters up to the ldisc.

Signed-Off-By: <schmitz@xxxxxxxxxx>

	Michael
--- linux-2.6.19-m68k-cvs/drivers/char/atari_scc.c	2006-12-09 14:28:33.000000000 +0100
+++ linux-2.6.19-m68k/drivers/char/atari_scc.c	2006-12-09 14:36:51.000000000 +0100
@@ -0,0 +1,1723 @@
+/*
+ * drivers/char/atari_scc.c: Atari TT/Falcon Am8530 SCC serial ports implementation.
+ * 
+ * Copyright 2005 Michael Schmitz
+ *
+ * Based on:
+ *   drivers/char/vme_scc.c: MVME147, MVME162, BVME6000 SCC serial ports
+ *   implementation.
+ *   Copyright 1999 Richard Hirst <richard@xxxxxxxxxxxxxxxxxxx>
+ *
+ * which, in turn, was
+ *
+ * Based on atari_SCC.c which was
+ *   Copyright 1994-95 Roman Hodek <Roman.Hodek@xxxxxxxxxxxxxxxxxxxxxxxxxx>
+ *   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 COPYING in the main directory of this archive
+ * for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kdev_t.h>
+#include <asm/io.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/mm.h>
+#include <linux/serial.h>
+#include <linux/fcntl.h>
+#include <linux/major.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/miscdevice.h>
+#include <linux/console.h>
+#include <linux/init.h>
+#include <asm/setup.h>
+#include <asm/uaccess.h>
+#include <asm/bootinfo.h>
+
+#include <asm/atarihw.h>
+#include <asm/atariints.h>
+
+#include <linux/generic_serial.h>
+#include "scc.h"
+
+#define CONFIG_TT_SCC     1
+#define CONFIG_FALCON_SCC 1
+
+#define CHANNEL_A	0
+#define CHANNEL_B	1
+
+#define SCC_MINOR_BASE	64
+
+/* Shadows for all SCC write registers */
+static unsigned char scc_shadow[2][16];
+
+/* Location to access for SCC register access delay */
+static volatile unsigned char *scc_del = NULL;
+
+/* To keep track of STATUS_REG state for detection of Ext/Status int source */
+static unsigned char scc_last_status_reg[2];
+
+/***************************** Prototypes *****************************/
+
+/* Function prototypes */
+static void scc_disable_tx_interrupts(void * ptr);
+static void scc_enable_tx_interrupts(void * ptr);
+static void scc_disable_rx_interrupts(void * ptr);
+static void scc_enable_rx_interrupts(void * ptr);
+static int  scc_get_CD(void * ptr);
+static void scc_shutdown_port(void * ptr);
+static int scc_set_real_termios(void  *ptr);
+static void scc_hungup(void  *ptr);
+static void scc_close(void  *ptr);
+static int scc_chars_in_buffer(void * ptr);
+static int scc_open(struct tty_struct * tty, struct file * filp);
+static int scc_ioctl(struct tty_struct * tty, struct file * filp,
+                     unsigned int cmd, unsigned long arg);
+static void scc_throttle(struct tty_struct *tty);
+static void scc_unthrottle(struct tty_struct *tty);
+static irqreturn_t scc_tx_int(int irq, void *data);
+static irqreturn_t scc_rx_int(int irq, void *data);
+static irqreturn_t scc_stat_int(int irq, void *data);
+static irqreturn_t scc_spcond_int(int irq, void *data);
+static void scc_setsignals(struct scc_port *port, int dtr, int rts);
+static void scc_break_ctl(struct tty_struct *tty, int break_state);
+
+static struct tty_driver *scc_driver;
+
+struct scc_port scc_ports[2];
+
+int scc_initialized = 0;
+
+/*
+ * Flags to indicate one of the serial ports has already been initialized by the 
+ * serial debug driver. We may want to hold off reinitializing ... 
+ */
+
+/* Flag that Modem1 port is already initialized and used */
+extern int atari_SCC_init_done;
+/* Can be set somewhere, if a SCC master reset has already be done and should
+ * not be repeated; used by kgdb */
+extern int atari_SCC_reset_done;
+
+/*---------------------------------------------------------------------------
+ * Interface from generic_serial.c back here
+ *--------------------------------------------------------------------------*/
+
+static struct real_driver scc_real_driver = {
+        scc_disable_tx_interrupts,
+        scc_enable_tx_interrupts,
+        scc_disable_rx_interrupts,
+        scc_enable_rx_interrupts,
+        scc_get_CD,
+        scc_shutdown_port,
+        scc_set_real_termios,
+        scc_chars_in_buffer,
+        scc_close,
+        scc_hungup,
+        NULL
+};
+
+static struct tty_operations scc_ops = {
+	.open	= scc_open,
+	.close = gs_close,
+	.write = gs_write,
+	.put_char = gs_put_char,
+	.flush_chars = gs_flush_chars,
+	.write_room = gs_write_room,
+	.chars_in_buffer = gs_chars_in_buffer,
+	.flush_buffer = gs_flush_buffer,
+	.ioctl = scc_ioctl,
+	.throttle = scc_throttle,
+	.unthrottle = scc_unthrottle,
+	.set_termios = gs_set_termios,
+	.stop = gs_stop,
+	.start = gs_start,
+	.hangup = gs_hangup,
+	.break_ctl = scc_break_ctl,
+};
+
+static unsigned SCC_clocks[2][2] = {
+	  /* RTxC */			/* TRxC */
+	{ SCC_BAUD_BASE_PCLK4,	SCC_BAUD_BASE_NONE },	/* Channel A */
+	{ SCC_BAUD_BASE_TIMC,	SCC_BAUD_BASE_BCLK }	/* Channel B */
+};
+
+/* BRG values for the standard speeds and the various clock sources */
+
+typedef struct {
+	unsigned	clksrc;		/* clock source to use or -1 for not possible */
+	unsigned	div;		/* divisor: 1, 2 and 4 correspond to
+					 * direct 1:16, 1:32 and 1:64 modes,
+					 * divisors >= 4 yield a BRG value of
+					 * div/2-2 (in 1:16 mode)
+					 */
+} BAUD_ENTRY;
+
+/* A pointer for each channel to the current baud table */
+static BAUD_ENTRY *scc_baud_table[2];
+
+/* Baud table format:
+ *
+ * Each entry consists of the clock source (CLK_RTxC, CLK_TRxC or
+ * CLK_PCLK) and a divisor. The following rules apply to the divisor:
+ *
+ *   - CLK_RTxC: 1 or even (1, 2 and 4 are the direct modes, > 4 use
+ *               the BRG)
+ *
+ *   - CLK_TRxC: 1, 2 or 4 (no BRG, only direct modes possible)
+ *
+ *   - CLK_PCLK: >= 4 and even (no direct modes, only BRG)
+ *
+ */
+
+/* This table is used if RTxC = 3.672 MHz. This is the case for TT's
+ * channel A and for both channels on the Mega STE/Falcon. (TRxC is unused)
+ */
+
+static BAUD_ENTRY bdtab_norm[20] = {
+	/* B0      */ { 0, 0 },
+	/* B50     */ { CLK_RTxC, 4590 },
+	/* B75     */ { CLK_RTxC, 3060 },
+	/* B110    */ { CLK_PCLK, 4576 },
+	/* B134    */ { CLK_PCLK, 3756 },
+	/* B150    */ { CLK_RTxC, 1530 },
+	/* B200    */ { CLK_PCLK, 2516 },
+	/* B300    */ { CLK_PCLK, 1678 },
+	/* B600    */ { CLK_PCLK, 838 },
+	/* B1200   */ { CLK_PCLK, 420 },
+	/* B1800   */ { CLK_PCLK, 280 },
+	/* B2400   */ { CLK_PCLK, 210 },
+	/* B4800   */ { CLK_RTxC, 48 },
+	/* B9600   */ { CLK_RTxC, 24 },
+	/* B19200  */ { CLK_RTxC, 12 },
+	/* B38400  */ { CLK_RTxC, 6 },   /* #15 spd_extra */
+	/* B57600  */ { CLK_RTxC, 4 },   /* #16 spd_hi */
+	/* B115200 */ { CLK_RTxC, 2 },   /* #17 spd_vhi */
+	/* B230400 */ { CLK_RTxC, 1 },   /* #18 spd_shi */
+	/* B460800 */ { 0, 0 }           /* #19 spd_warp: Impossible */
+};
+
+/* This is a special table for the TT channel B with 307.2 kHz at RTxC
+ * and 2.4576 MHz at TRxC
+ */
+static BAUD_ENTRY bdtab_TTChB[20] = {
+	/* B0      */ { 0, 0 },
+	/* B50     */ { CLK_RTxC, 384 },
+	/* B75     */ { CLK_RTxC, 256 },
+	/* B110    */ { CLK_PCLK, 4576 },
+	/* B134    */ { CLK_PCLK, 3756 },
+	/* B150    */ { CLK_RTxC, 128 },
+	/* B200    */ { CLK_RTxC, 96 },
+	/* B300    */ { CLK_RTxC, 64 },
+	/* B600    */ { CLK_RTxC, 32 },
+	/* B1200   */ { CLK_RTxC, 16 },
+	/* B1800   */ { CLK_PCLK, 280 },
+	/* B2400   */ { CLK_RTxC, 8 },
+	/* B4800   */ { CLK_RTxC, 4 },
+	/* B9600   */ { CLK_RTxC, 2 },
+	/* B19200  */ { CLK_RTxC, 1 },
+	/* B38400  */ { CLK_TRxC, 4 },
+	/* B57600  */ { CLK_TRxC, 2 }, /* 57600 is not possible, use 76800 instead */
+	/* B115200 */ { CLK_TRxC, 1 }, /* 115200 is not possible, use 153600 instead */
+	/* B230400 */ { 0, 0 },        /* #18 spd_shi: Impossible  */
+	/* B460800 */ { 0, 0 }         /* #19 spd_warp: Impossible */
+};
+
+
+/*----------------------------------------------------------------------------
+ * atari_scc_init() and support functions
+ *---------------------------------------------------------------------------*/
+
+static int scc_init_drivers(void)
+{
+	int error;
+
+	scc_driver = alloc_tty_driver(2);
+	if (!scc_driver)
+		return -ENOMEM;
+	scc_driver->owner = THIS_MODULE;
+	scc_driver->driver_name = "scc";
+	scc_driver->name = "ttyS";
+	scc_driver->major = TTY_MAJOR;
+	scc_driver->minor_start = SCC_MINOR_BASE;
+	scc_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	scc_driver->subtype = SERIAL_TYPE_NORMAL;
+	scc_driver->init_termios = tty_std_termios;
+	scc_driver->init_termios.c_cflag =
+	  B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	scc_driver->flags = TTY_DRIVER_REAL_RAW;
+
+	tty_set_operations(scc_driver, &scc_ops);
+
+	if ((error = tty_register_driver(scc_driver))) {
+		printk(KERN_ERR "scc: Couldn't register scc driver, error = %d\n",
+		       error);
+		put_tty_driver(scc_driver);
+		return 1;
+	}
+
+	return 0;
+}
+
+
+/* ports[] array is indexed by line no (i.e. [0] for ttyS0, [1] for ttyS1).
+ */
+
+static void scc_init_portstructs(void)
+{
+	struct scc_port *port;
+	int i;
+
+	for (i = 0; i < 2; i++) {
+		port = scc_ports + i;
+		port->gs.magic = SCC_MAGIC;
+		port->gs.close_delay = HZ/2;
+		port->gs.closing_wait = 30 * HZ;
+		port->gs.rd = &scc_real_driver;
+#ifdef NEW_WRITE_LOCKING
+		port->gs.port_write_sem = MUTEX;
+#endif
+		init_waitqueue_head(&port->gs.open_wait);
+		init_waitqueue_head(&port->gs.close_wait);
+	}
+}
+
+
+#ifdef CONFIG_TT_SCC
+static int atari_tt_scc_init(void)
+{
+	struct scc_port *port;
+
+	printk(KERN_INFO "SCC: Atari TT Serial Driver\n");
+	/* FIXME channel A may be switchable between modem and LAN port */
+	/* Init channel A */
+	if (atari_SCC_init_done)
+		printk(KERN_INFO "SCC: already initialized, expect trouble!\n");
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: init channel A\n");
+#endif
+	port = &scc_ports[0];
+	port->channel = CHANNEL_A;
+	port->ctrlp = (volatile unsigned char *)&scc.cha_a_ctrl;
+	port->datap = port->ctrlp + 1;
+	port->port_a = &scc_ports[0];
+	port->port_b = &scc_ports[1];
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: request channel A irqs, port = %p\n", port);
+#endif
+	request_irq(IRQ_SCCA_TX, scc_tx_int, IRQ_TYPE_PRIO,
+		            "SCC-A TX", port);
+	request_irq(IRQ_SCCA_STAT, scc_stat_int, IRQ_TYPE_PRIO,
+		            "SCC-A status", port);
+	request_irq(IRQ_SCCA_RX, scc_rx_int, IRQ_TYPE_PRIO,
+		            "SCC-A RX", port);
+	request_irq(IRQ_SCCA_SPCOND, scc_spcond_int, IRQ_TYPE_PRIO,
+		            "SCC-A special cond", port);
+	{
+		SCC_ACCESS_INIT(port);
+#if defined(DEBUG)
+		printk(KERN_INFO "SCC: read SCC status\n");
+#endif
+		/* on the first access, read status register to reset internal pointers */
+		SCCread(STATUS_REG);
+#if defined(DEBUG)
+		printk(KERN_INFO "SCC: reset SCC\n");
+#endif
+		/* FIXME: master reset, once only */
+		SCCwrite(MASTER_INT_CTRL, MIC_HARD_RESET);
+		udelay(40);
+
+		/* disable interrupts for this channel */
+		SCCwrite(INT_AND_DMA_REG, 0);
+		/* Set the interrupt vector ; 0x60 for all Atari models */
+		SCCwrite(INT_VECTOR_REG, 0x60);
+		/* Interrupt parameters: vector includes status, status low */
+		SCCwrite(MASTER_INT_CTRL, MIC_VEC_INCL_STAT);
+		SCCmod(MASTER_INT_CTRL, 0xff, MIC_MASTER_INT_ENAB);
+
+		/* disable interrupts for this channel */
+		SCCwrite(INT_AND_DMA_REG, 0);
+	}
+
+	if (!atari_SCC_init_done) {
+	/* Init channel B */
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: init channel B\n");
+#endif
+	port = &scc_ports[1];
+	port->channel = CHANNEL_B;
+	port->ctrlp = (volatile unsigned char *)&scc.cha_b_ctrl;
+	port->datap = port->ctrlp + 1;
+	port->port_a = &scc_ports[0];
+	port->port_b = &scc_ports[1];
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: request channel B irqs, port = %p\n", port);
+#endif
+	request_irq(IRQ_SCCB_TX, scc_tx_int, IRQ_TYPE_PRIO,
+		            "SCC-B TX", port);
+	request_irq(IRQ_SCCB_STAT, scc_stat_int, IRQ_TYPE_PRIO,
+		            "SCC-B status", port);
+	request_irq(IRQ_SCCB_RX, scc_rx_int, IRQ_TYPE_PRIO,
+		            "SCC-B RX", port);
+	request_irq(IRQ_SCCB_SPCOND, scc_spcond_int, IRQ_TYPE_PRIO,
+		            "SCC-B special cond", port);
+	{
+		SCC_ACCESS_INIT(port);
+
+		/* disable interrupts for this channel */
+		SCCwrite(INT_AND_DMA_REG, 0);
+	}
+/* not implemented yet */
+#if 0
+	request_irq(IRQ_TT_MFP_RI, scc_ri_int, IRQ_TYPE_SLOW,
+		     "TT-MFP ring indicator (modem 2)", port);
+#endif
+
+	}
+
+	/* once only: initalize MFP timer C for RTxC */
+	tt_mfp.tim_ct_cd = (tt_mfp.tim_ct_cd & ~0x70) | 0x10;
+	tt_mfp.tim_dt_c = 1;
+	atari_turnoff_irq(IRQ_TT_MFP_TIMC);
+
+	/* set baud tables */
+	scc_baud_table[CHANNEL_A] = bdtab_norm;
+	scc_baud_table[CHANNEL_B] = bdtab_TTChB;
+
+	/* Initialise the tty driver structures and register */
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: scc_init_portstructs()\n");
+#endif
+	scc_init_portstructs();
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: scc_init_drivers()\n");
+#endif
+	scc_init_drivers();
+
+	return 0;
+}
+#endif
+
+
+#ifdef CONFIG_FALCON_SCC
+static int atari_falcon_scc_init(void)
+{
+	struct scc_port *port;
+
+	printk(KERN_INFO "SCC: Atari Falcon Serial Driver\n");
+	if (atari_SCC_init_done)
+		printk(KERN_INFO "SCC: already initialized, expect trouble!\n");
+
+	/* Init channel A */
+	port = &scc_ports[0];
+	port->channel = CHANNEL_A;
+	port->ctrlp = (volatile unsigned char *)&scc.cha_a_ctrl;
+	port->datap = port->ctrlp + 2;
+	port->port_a = &scc_ports[0];
+	port->port_b = &scc_ports[1];
+	request_irq(IRQ_SCCA_TX, scc_tx_int, IRQ_TYPE_PRIO,
+		            "SCC-A TX", port);
+	request_irq(IRQ_SCCA_STAT, scc_stat_int, IRQ_TYPE_PRIO,
+		            "SCC-A status", port);
+	request_irq(IRQ_SCCA_RX, scc_rx_int, IRQ_TYPE_PRIO,
+		            "SCC-A RX", port);
+	request_irq(IRQ_SCCA_SPCOND, scc_spcond_int, IRQ_TYPE_PRIO,
+		            "SCC-A special cond", port);
+	{
+		SCC_ACCESS_INIT(port);
+
+		/* on the first access, read status register to reset internal pointers */
+		SCCread(STATUS_REG);
+
+		/* FIXME: master reset, once only */
+		SCCwrite(MASTER_INT_CTRL, MIC_HARD_RESET);
+		udelay(40);
+
+		/* disable interrupts for this channel */
+		SCCwrite(INT_AND_DMA_REG, 0);
+		/* Set the interrupt vector */
+		SCCwrite(INT_VECTOR_REG, 0x60);
+		/* Interrupt parameters: vector includes status, status low */
+		SCCwrite(MASTER_INT_CTRL, MIC_VEC_INCL_STAT);
+		SCCmod(MASTER_INT_CTRL, 0xff, MIC_MASTER_INT_ENAB);
+	}
+
+	/* conditionalize if port in use by console ?? */
+	/* Init channel B */
+	port = &scc_ports[1];
+	port->channel = CHANNEL_B;
+	port->ctrlp = (volatile unsigned char *)&scc.cha_b_ctrl;
+	port->datap = port->ctrlp + 2;
+	port->port_a = &scc_ports[0];
+	port->port_b = &scc_ports[1];
+	request_irq(IRQ_SCCB_TX, scc_tx_int, IRQ_TYPE_PRIO,
+		            "SCC-B TX", port);
+	request_irq(IRQ_SCCB_STAT, scc_stat_int, IRQ_TYPE_PRIO,
+		            "SCC-B status", port);
+	request_irq(IRQ_SCCB_RX, scc_rx_int, IRQ_TYPE_PRIO,
+		            "SCC-B RX", port);
+	request_irq(IRQ_SCCB_SPCOND, scc_spcond_int, IRQ_TYPE_PRIO,
+		            "SCC-B special cond", port);
+
+	{
+		SCC_ACCESS_INIT(port);	/* Either channel will do */
+
+		/* disable interrupts for this channel */
+		SCCwrite(INT_AND_DMA_REG, 0);
+	}
+
+	/* set baud tables */
+	scc_baud_table[CHANNEL_A] = bdtab_norm;
+	scc_baud_table[CHANNEL_B] = bdtab_norm;
+
+	/* Initialise the tty driver structures and register */
+	scc_init_portstructs();
+	scc_init_drivers();
+
+	return 0;
+}
+#endif
+
+
+#ifdef CONFIG_ST_SCC
+static int atari_st_scc_init(void)
+{
+	struct scc_port *port;
+
+	int escc = ATARIHW_PRESENT(ST_ESCC);
+
+	printk(KERN_INFO "SCC: Atari MegaST/E Serial Driver\n");
+	/* FIXME: ports reversed logic */
+	/* Init channel A */
+	port = &scc_ports[1];
+	port->channel = CHANNEL_A;
+	port->ctrlp = (volatile unsigned char *)(escc ? &st_escc.cha_a_ctrl : &scc.cha_a_ctrl);
+	port->datap = port->ctrlp + 4;
+	port->port_a = &scc_ports[1];
+	port->port_b = &scc_ports[0];
+	request_irq(IRQ_SCCA_TX, scc_tx_int, IRQ_TYPE_PRIO,
+		            "SCC-A TX", port);
+	request_irq(IRQ_SCCA_STAT, scc_stat_int, IRQ_TYPE_PRIO,
+		            "SCC-A status", port);
+	request_irq(IRQ_SCCA_RX, scc_rx_int, IRQ_TYPE_PRIO,
+		            "SCC-A RX", port);
+	request_irq(SCCA_SPCOND, scc_spcond_int, IRQ_TYPE_PRIO,
+		            "SCC-A special cond", port);
+	{
+		SCC_ACCESS_INIT(port);
+
+		/* on the first access, read status register to reset internal pointers */
+		SCCread(STATUS_REG);
+
+		/* FIXME: master reset, once only */
+		SCCwrite(MASTER_INT_CTRL, MIC_HARD_RESET);
+		udelay(40);
+
+		/* disable interrupts for this channel */
+		SCCwrite(INT_AND_DMA_REG, 0);
+		/* Set the interrupt vector */
+		SCCwrite(INT_VECTOR_REG, BVME_IRQ_SCC_BASE);
+		/* Interrupt parameters: vector includes status, status low */
+		SCCwrite(MASTER_INT_CTRL, MIC_VEC_INCL_STAT);
+		SCCmod(MASTER_INT_CTRL, 0xff, MIC_MASTER_INT_ENAB);
+	}
+
+	/* Init channel B */
+	port = &scc_ports[0];
+	port->channel = CHANNEL_B;
+	port->ctrlp = (volatile unsigned char *)(escc ? &st_escc.cha_b_ctrl : &scc.cha_b_ctrl);
+	port->datap = port->ctrlp + 4;
+	port->port_a = &scc_ports[0];
+	port->port_b = &scc_ports[1];
+	request_irq(IRQ_SCCB_TX, scc_tx_int, IRQ_TYPE_PRIO,
+		            "SCC-B TX", port);
+	request_irq(IRQ_SCCB_STAT, scc_stat_int, IRQ_TYPE_PRIO,
+		            "SCC-B status", port);
+	request_irq(IRQ_SCCB_RX, scc_rx_int, IRQ_TYPE_PRIO,
+		            "SCC-B RX", port);
+	request_irq(IRQ_SCCB_SPCOND, scc_spcond_int, IRQ_TYPE_PRIO,
+		            "SCC-B special cond", port);
+
+	{
+		SCC_ACCESS_INIT(port);	/* Either channel will do */
+
+		/* disable interrupts for this channel */
+		SCCwrite(INT_AND_DMA_REG, 0);
+	}
+
+	/* set baud tables */
+	scc_baud_table[CHANNEL_A] = bdtab_norm;
+	scc_baud_table[CHANNEL_B] = bdtab_norm;
+
+	/* Initialise the tty driver structures and register */
+	scc_init_portstructs();
+	scc_init_drivers();
+
+	return 0;
+}
+#endif
+
+
+int atari_scc_init(void)
+{
+	int res = -ENODEV;
+	static int called = 0;
+
+	if (called)
+		return res;
+	called = 1;
+
+        if (!(ATARIHW_PRESENT(SCC) || ATARIHW_PRESENT(ST_ESCC)))
+                        return( -ENODEV );
+	
+	scc_del = &mfp.par_dt_reg;
+
+#ifdef CONFIG_TT_SCC
+	if (MACH_IS_TT)
+		res = atari_tt_scc_init();
+#endif
+#ifdef CONFIG_FALCON_SCC
+	if (MACH_IS_FALCON)
+		res = atari_falcon_scc_init();
+#endif
+#ifdef CONFIG_ST_SCC
+	if (MACH_IS_ST)
+		res = atari_st_scc_init();
+#endif
+	return res;
+}
+
+void atari_scc_cleanup(void)
+{
+	struct scc_port *port;
+
+	tty_unregister_driver(scc_driver);
+	port = &scc_ports[0];
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: free channel A irqs, port = %p\n", port);
+#endif
+	free_irq(IRQ_SCCA_TX, port);
+	free_irq(IRQ_SCCA_STAT, port);
+	free_irq(IRQ_SCCA_RX, port);
+	free_irq(IRQ_SCCA_SPCOND, port);
+
+	port = &scc_ports[1];
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: free channel A irqs, port = %p\n", port);
+#endif
+	free_irq(IRQ_SCCB_TX, port);
+	free_irq(IRQ_SCCB_STAT, port);
+	free_irq(IRQ_SCCB_RX, port);
+	free_irq(IRQ_SCCB_SPCOND, port);
+
+}
+
+module_init(atari_scc_init);
+module_exit(atari_scc_cleanup);
+
+/*---------------------------------------------------------------------------
+ * Interrupt handlers
+ *--------------------------------------------------------------------------*/
+
+static irqreturn_t scc_rx_int(int irq, void *data)
+{
+	unsigned char	ch;
+	struct scc_port *port = data;
+	struct tty_struct *tty = port->gs.tty;
+	SCC_ACCESS_INIT(port);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: rx_int ...\n");
+#endif
+	ch = SCCread_NB(RX_DATA_REG);
+	if (!tty) {
+		printk(KERN_WARNING "scc_rx_int with NULL tty!\n");
+		SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+		return IRQ_HANDLED;
+	}
+   	tty_insert_flip_char(tty, ch, 0);
+#if 0
+	if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+		*tty->flip.char_buf_ptr = ch;
+		*tty->flip.flag_buf_ptr = 0;
+		tty->flip.flag_buf_ptr++;
+		tty->flip.char_buf_ptr++;
+		tty->flip.count++;
+	}
+#endif
+	/* Check if another character is already ready; in that case, the
+	 * spcond_int() function must be used, because this character may have an
+	 * error condition that isn't signalled by the interrupt vector used!
+	 */
+	if (SCCread(INT_PENDING_REG) &
+	    (port->channel == CHANNEL_A ? IPR_A_RX : IPR_B_RX)) {
+		scc_spcond_int (irq, data);
+		return IRQ_HANDLED;
+	}
+
+	SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+
+	tty_flip_buffer_push(tty);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: rx_int done\n");
+#endif
+	return IRQ_HANDLED;
+}
+
+
+static irqreturn_t scc_spcond_int(int irq, void *data)
+{
+	struct scc_port *port = data;
+	struct tty_struct *tty = port->gs.tty;
+	unsigned char	stat, ch, err;
+	int		int_pending_mask = port->channel == CHANNEL_A ?
+			                   IPR_A_RX : IPR_B_RX;
+	SCC_ACCESS_INIT(port);
+#if defined(DEBUG)	
+	printk(KERN_INFO "SCC: spcond_int ...\n");
+#endif
+	if (!tty) {
+		printk(KERN_WARNING "scc_spcond_int with NULL tty!\n");
+		SCCwrite(COMMAND_REG, CR_ERROR_RESET);
+		SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+		return IRQ_HANDLED;
+	}
+	do {
+		stat = SCCread(SPCOND_STATUS_REG);
+		ch = SCCread_NB(RX_DATA_REG);
+
+		if (stat & SCSR_RX_OVERRUN)
+			err = TTY_OVERRUN;
+		else if (stat & SCSR_PARITY_ERR)
+			err = TTY_PARITY;
+		else if (stat & SCSR_CRC_FRAME_ERR)
+			err = TTY_FRAME;
+		else
+			err = 0;
+
+	    	tty_insert_flip_char(tty, ch, err);
+#if 0
+		if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+			*tty->flip.char_buf_ptr = ch;
+			*tty->flip.flag_buf_ptr = err;
+			tty->flip.flag_buf_ptr++;
+			tty->flip.char_buf_ptr++;
+			tty->flip.count++;
+		}
+#endif
+		/* ++TeSche: *All* errors have to be cleared manually,
+		 * else the condition persists for the next chars
+		 */
+		if (err)
+		  SCCwrite(COMMAND_REG, CR_ERROR_RESET);
+
+	} while(SCCread(INT_PENDING_REG) & int_pending_mask);
+
+	SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+
+	tty_flip_buffer_push(tty);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: spcond_int done\n");
+#endif
+	return IRQ_HANDLED;
+}
+
+/* not implemented yet */
+#if 0
+static void scc_ri_int(int irq, void *data)
+{
+	struct scc_port *port = data;
+	/* update input line counter */
+	port->icount.rng++;
+	wake_up_interruptible(&port->delta_msr_wait);
+}
+#endif
+
+static irqreturn_t scc_tx_int(int irq, void *data)
+{
+	struct scc_port *port = data;
+	SCC_ACCESS_INIT(port);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: tx_int irq %d port %p ...\n", irq, data);
+#endif
+	if (!port->gs.tty) {
+		printk(KERN_WARNING "scc_tx_int with NULL tty!\n");
+		SCCmod (INT_AND_DMA_REG, ~IDR_TX_INT_ENAB, 0);
+		SCCwrite(COMMAND_REG, CR_TX_PENDING_RESET);
+		SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+		return IRQ_HANDLED;
+	}
+	while ((SCCread_NB(STATUS_REG) & SR_TX_BUF_EMPTY)) {
+		if (port->x_char) {
+#if defined(DEBUG)
+			printk(KERN_INFO "SCC: tx_int writing char %c\n", port->x_char);
+#endif
+			SCCwrite(TX_DATA_REG, port->x_char);
+			port->x_char = 0;
+		}
+		else if ((port->gs.xmit_cnt <= 0) || port->gs.tty->stopped ||
+				port->gs.tty->hw_stopped) {
+#if defined(DEBUG)
+			printk(KERN_INFO "SCC: nothing to do!\n");
+#endif
+			break;
+		} else {
+#if defined(DEBUG)
+			printk(KERN_INFO "SCC: tx_int writing buf %c\n", port->gs.xmit_buf[port->gs.xmit_tail]);
+#endif
+			SCCwrite(TX_DATA_REG, port->gs.xmit_buf[port->gs.xmit_tail++]);
+			port->gs.xmit_tail = port->gs.xmit_tail & (SERIAL_XMIT_SIZE-1);
+			if (--port->gs.xmit_cnt <= 0)
+				break;
+		}
+	}
+	if ((port->gs.xmit_cnt <= 0) || port->gs.tty->stopped ||
+			port->gs.tty->hw_stopped) {
+#if defined(DEBUG)
+		printk(KERN_INFO "SCC: nothing to do, disabling int\n");
+#endif
+		/* disable tx interrupts */
+		SCCmod (INT_AND_DMA_REG, ~IDR_TX_INT_ENAB, 0);
+		SCCwrite(COMMAND_REG, CR_TX_PENDING_RESET);   /* disable tx_int on next tx underrun? */
+		port->gs.flags &= ~GS_TX_INTEN;
+	}
+	if (port->gs.tty && port->gs.xmit_cnt <= port->gs.wakeup_chars) {
+#if defined(DEBUG)
+		printk(KERN_INFO "SCC: waking up tty!\n");
+#endif
+		tty_wakeup(port->gs.tty);
+	}
+
+	SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: tx_int done\n");
+#endif
+	return IRQ_HANDLED;
+}
+
+
+static irqreturn_t scc_stat_int(int irq, void *data)
+{
+	struct scc_port *port = data;
+	unsigned channel = port->channel;
+	unsigned char	last_sr, sr, changed;
+	SCC_ACCESS_INIT(port);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: stat_int ...\n");
+#endif
+	last_sr = scc_last_status_reg[channel];
+	sr = scc_last_status_reg[channel] = SCCread_NB(STATUS_REG);
+	changed = last_sr ^ sr;
+
+	if (changed & SR_DCD) {
+		port->c_dcd = !!(sr & SR_DCD);
+		if (!(port->gs.flags & ASYNC_CHECK_CD))
+			;	/* Don't report DCD changes */
+		else if (port->c_dcd) {
+			/* Are we blocking in open?*/
+			wake_up_interruptible(&port->gs.open_wait);
+		}
+		else {
+			if (port->gs.tty)
+				tty_hangup (port->gs.tty);
+		}
+	}
+
+	// FIXME: CTS and DSR status changes? 
+
+	SCCwrite(COMMAND_REG, CR_EXTSTAT_RESET);
+	SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: stat_int done\n");
+#endif
+	return IRQ_HANDLED;
+}
+
+
+/*---------------------------------------------------------------------------
+ * generic_serial.c callback funtions
+ *--------------------------------------------------------------------------*/
+
+static void scc_disable_tx_interrupts(void *ptr)
+{
+	struct scc_port *port = ptr;
+	unsigned long	flags;
+	SCC_ACCESS_INIT(port);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: disable_tx_int ...\n");
+#endif
+	local_irq_save(flags);
+	SCCmod(INT_AND_DMA_REG, ~IDR_TX_INT_ENAB, 0);
+	port->gs.flags &= ~GS_TX_INTEN;
+	local_irq_restore(flags);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: disable_tx_int done!\n");
+#endif
+}
+
+
+static void scc_enable_tx_interrupts(void *ptr)
+{
+	struct scc_port *port = ptr;
+	unsigned long	flags;
+	SCC_ACCESS_INIT(port);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: enable_tx_int ...\n");
+#endif
+	local_irq_save(flags);
+	SCCmod(INT_AND_DMA_REG, 0xff, IDR_TX_INT_ENAB);
+	/* restart the transmitter */
+	scc_tx_int (0, port);
+	local_irq_restore(flags);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: enable_tx_int done!\n");
+#endif
+}
+
+
+static void scc_disable_rx_interrupts(void *ptr)
+{
+	struct scc_port *port = ptr;
+	unsigned long	flags;
+	SCC_ACCESS_INIT(port);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: disable_rx_int ...\n");
+#endif
+	local_irq_save(flags);
+	SCCmod(INT_AND_DMA_REG,
+	    ~(IDR_RX_INT_MASK|IDR_PARERR_AS_SPCOND|IDR_EXTSTAT_INT_ENAB), 0);
+	local_irq_restore(flags);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: disable_rx_int done!\n");
+#endif
+}
+
+
+static void scc_enable_rx_interrupts(void *ptr)
+{
+	struct scc_port *port = ptr;
+	unsigned long	flags;
+	SCC_ACCESS_INIT(port);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: enable_rx_int ...\n");
+#endif
+	local_irq_save(flags);
+	SCCmod(INT_AND_DMA_REG, 0xff,
+		IDR_EXTSTAT_INT_ENAB|IDR_PARERR_AS_SPCOND|IDR_RX_INT_ALL);
+	local_irq_restore(flags);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: enable_rx_int done!\n");
+#endif
+}
+
+
+static int scc_get_CD(void *ptr)
+{
+	struct scc_port *port = ptr;
+	unsigned channel = port->channel;
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: get_CD!\n");
+#endif
+	return !!(scc_last_status_reg[channel] & SR_DCD);
+}
+
+
+static void scc_shutdown_port(void *ptr)
+{
+	struct scc_port *port = ptr;
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: shutdown_port ...\n");
+#endif
+	port->gs.flags &= ~ GS_ACTIVE;
+	if (port->gs.tty && port->gs.tty->termios->c_cflag & HUPCL) {
+		scc_setsignals (port, 0, 0);
+	}
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: shutdown_port done!\n");
+#endif
+}
+
+
+static int scc_set_real_termios (void *ptr)
+{
+	/* the SCC has char sizes 5,7,6,8 in that order! */
+	static int chsize_map[4] = { 0, 2, 1, 3 };
+	unsigned cflag, baud, baudbits, baudidx, brgmode, clkmode, clksrc, div, chsize, channel, brgval = 0;
+	unsigned long flags;
+	struct scc_port *port = ptr;
+	SCC_ACCESS_INIT(port);
+
+	if (!port->gs.tty || !port->gs.tty->termios) return 0;
+
+	channel = port->channel;
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: termios for channel %p\n", channel);
+#endif
+	cflag  = port->gs.tty->termios->c_cflag;
+	baud = port->gs.baud;
+	baudbits = cflag & CBAUD;
+	chsize = (cflag & CSIZE) >> 4;
+
+	if (baud == 0) {
+		/* speed == 0 -> drop DTR */
+		local_irq_save(flags);
+		SCCmod(TX_CTRL_REG, ~TCR_DTR, 0);
+		local_irq_restore(flags);
+		return 0;
+	}
+	else if ((MACH_IS_TT && (baud < 50 || baud > 115200)) ||
+		 (MACH_IS_FALCON && (baud < 50 || baud > 230400))) {
+		printk(KERN_NOTICE "SCC: Bad speed requested, %d\n", baud);
+		return 0;
+	}
+
+	if (cflag & CLOCAL)
+		port->gs.flags &= ~ASYNC_CHECK_CD;
+	else
+		port->gs.flags |= ASYNC_CHECK_CD;
+
+	// calculate brgval for Atari; enable direct modes! 
+
+	/* convert baud rate from gs.baud to table index, set custom divisor eventually */
+
+	div     = 0;
+	clksrc  = 0;
+	baudidx = 0;
+
+	switch (baud) {
+	case 50:
+	  baudidx = 1; 
+	  break;
+	case 75:
+	  baudidx = 2; 
+	  break;
+	case 110:
+	  baudidx = 3; 
+	  break;
+	case 134:
+	  baudidx = 4; 
+	  break;
+	case 150:
+	  baudidx = 5; 
+	  break;
+	case 200:
+	  baudidx = 6; 
+	  break;
+	case 300:
+	  baudidx = 7; 
+	  break;
+	case 600:
+	  baudidx = 8; 
+	  break;
+	case 1200:
+	  baudidx = 9; 
+	  break;
+	case 1800:
+	  baudidx = 10; 
+	  break;
+	case 2400:
+	  baudidx = 11; 
+	  break;
+	case 4800:
+	  baudidx = 12; 
+	  break;
+	case 9600:
+	  baudidx = 13; 
+	  break;
+	case 19200:
+	  baudidx = 14; 
+	  break;
+	case 38400:
+	  baudidx = 15; 
+	  break;
+	case 57600:
+	  baudidx = 16; 
+	  break;
+	case 115200:
+	  baudidx = 17; 
+	  break;
+	case 230400:
+	  baudidx = 18; 
+	  break;
+	default:
+	  baudidx = 15;
+	}
+
+	/* do we have a custom divisor ?? */
+	if (!div) {
+		if (baudidx > 19) baudidx = 19;
+		clksrc = scc_baud_table[channel][baudidx].clksrc;
+		div = scc_baud_table[channel][baudidx].div;
+		if(!div)
+		{
+			printk(" SCC_change_speed: divisor = 0 !!!");
+			return 0;
+		}
+	}
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: termios baud %d baudbits %d baudidx %d \n clksrc %d div %d\n",
+		baud, baudbits, baudidx, clksrc, div);
+#endif
+	/* compute the SCC's clock source, clock mode, BRG mode and BRG
+	 * value from clksrc and div
+	 */
+	if (div <= 4) {
+		clkmode = (div == 1 ? A1CR_CLKMODE_x16 :
+			   div == 2 ? A1CR_CLKMODE_x32 :
+				      A1CR_CLKMODE_x64);
+		clksrc  = (clksrc == CLK_RTxC
+			   ? CCR_TXCLK_RTxC | CCR_RXCLK_RTxC
+			   : CCR_TXCLK_TRxC | CCR_RXCLK_TRxC);
+		brgmode = 0; /* off */
+		brgval  = 0;
+	}
+	else {
+		brgval  = div/2 - 2;
+		brgmode = (DCR_BRG_ENAB |
+			   (clksrc == CLK_PCLK ? DCR_BRG_USE_PCLK : 0));
+		clkmode = A1CR_CLKMODE_x16;
+		clksrc  = CCR_TXCLK_BRG | CCR_RXCLK_BRG;
+	}
+
+	//printk(KERN_INFO "SCC: termios baud %d baudbits %d baudidx %d \n clksrc %d clkmode %d div %d brgval %d brgmode %d\n",
+	//	baud, baudbits, baudidx, clksrc, clkmode, div, brgval, brgmode);
+
+	/* Now we have all parameters and can go to set them: */
+	local_irq_save(flags);
+
+#if defined(DEBUG)
+	printk( "  brgval=%d brgmode=%02x clkmode=%02x clksrc=%02x\n",
+			brgval, brgmode, clkmode, clksrc );
+#endif
+	/* receiver's character size and auto-enables */
+#if 0	// auto-enable considered harmful ... 
+	SCCmod(RX_CTRL_REG, ~(RCR_CHSIZE_MASK|RCR_AUTO_ENAB_MODE),
+			(chsize_map[chsize] << 6) |
+			((cflag & CRTSCTS) ? RCR_AUTO_ENAB_MODE : 0));
+#else
+	/* receiver's character size */
+	SCCmod( RX_CTRL_REG, ~RCR_CHSIZE_MASK, chsize_map[chsize] << 6 );
+#endif
+#if defined(DEBUG)
+	printk( "  RX_CTRL_REG <- %02x\n", SCCread( RX_CTRL_REG ) );
+#endif
+
+	// clock mode changes depending on baud rate
+	/* parity and stop bits (both, Tx and Rx) and clock mode */
+	SCCmod (AUX1_CTRL_REG,
+		~(A1CR_PARITY_MASK | A1CR_MODE_MASK | A1CR_CLKMODE_MASK),
+		((cflag & PARENB
+		  ? (cflag & PARODD ? A1CR_PARITY_ODD : A1CR_PARITY_EVEN)
+		  : A1CR_PARITY_NONE)
+		 | (cflag & CSTOPB ? A1CR_MODE_ASYNC_2 : A1CR_MODE_ASYNC_1)
+		 | clkmode));
+
+#if defined(DEBUG)
+	printk( "  AUX1_CTRL_REG <- %02x\n", SCCread( AUX1_CTRL_REG ) );
+#endif
+	/* sender's character size, set DTR for valid baud rate */
+	SCCmod(TX_CTRL_REG, ~TCR_CHSIZE_MASK, chsize_map[chsize] << 5 | TCR_DTR);
+#if defined(DEBUG)
+	printk( "  TX_CTRL_REG <- %02x\n", SCCread( TX_CTRL_REG ) );
+#endif
+
+	// clock sources change for TT !!
+	/* clock sources never change */
+	/* clock sources */
+	SCCmod( CLK_CTRL_REG, ~(CCR_TXCLK_MASK | CCR_RXCLK_MASK), clksrc );
+#if defined(DEBUG)
+	printk( "  CLK_CTRL_REG <- %02x\n", SCCread( CLK_CTRL_REG ) );
+#endif
+
+	/* disable BRG before changing the value */
+	SCCmod(DPLL_CTRL_REG, ~DCR_BRG_ENAB, 0);
+	/* BRG value */
+	SCCwrite(TIMER_LOW_REG, brgval & 0xff);
+	SCCwrite(TIMER_HIGH_REG, (brgval >> 8) & 0xff);
+	/* BRG enable, and clock source never changes */
+	//SCCmod(DPLL_CTRL_REG, 0xff, DCR_BRG_ENAB);
+	SCCmod(DPLL_CTRL_REG, ~(DCR_BRG_ENAB | DCR_BRG_USE_PCLK), brgmode);
+#if defined(DEBUG)
+	printk( "  TIMER_LOW_REG <- %02x\n", SCCread( TIMER_LOW_REG ) );
+	printk( "  TIMER_HIGH_REG <- %02x\n", SCCread( TIMER_HIGH_REG ) );
+#endif
+#if defined(DEBUG)
+	printk( "  DPLL_CTRL_REG <- %02x\n", SCCread( DPLL_CTRL_REG ) );
+#endif
+
+	local_irq_restore(flags);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: done termios for channel %d\n", channel);
+#endif
+	return 0;
+}
+
+
+static int scc_chars_in_buffer (void *ptr)
+{
+	struct scc_port *port = ptr;
+#if defined(DEBUG)
+	int rv;
+#endif
+	SCC_ACCESS_INIT(port);
+#if defined(DEBUG)
+	rv = (SCCread (SPCOND_STATUS_REG) & SCSR_ALL_SENT) ? 0  : 1;
+	printk(KERN_INFO "SCC: chars_in_buffer: %d\n", rv);
+	return rv;
+#else
+	return (SCCread (SPCOND_STATUS_REG) & SCSR_ALL_SENT) ? 0  : 1;
+#endif
+}
+
+
+/* Comment taken from sx.c (2.4.0):
+   I haven't the foggiest why the decrement use count has to happen
+   here. The whole linux serial drivers stuff needs to be redesigned.
+   My guess is that this is a hack to minimize the impact of a bug
+   elsewhere. Thinking about it some more. (try it sometime) Try
+   running minicom on a serial port that is driven by a modularized
+   driver. Have the modem hangup. Then remove the driver module. Then
+   exit minicom.  I expect an "oops".  -- REW */
+
+static void scc_hungup(void *ptr)
+{
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: hungup ...\n");
+#endif
+	scc_disable_tx_interrupts(ptr);
+	scc_disable_rx_interrupts(ptr);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: hungup done\n");
+#endif
+}
+
+
+static void scc_close(void *ptr)
+{
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: close ...\n");
+#endif
+	scc_disable_tx_interrupts(ptr);
+	scc_disable_rx_interrupts(ptr);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: close done\n");
+#endif
+}
+
+
+/*---------------------------------------------------------------------------
+ * Internal support functions
+ *--------------------------------------------------------------------------*/
+
+static void scc_setsignals(struct scc_port *port, int dtr, int rts)
+{
+	unsigned long flags;
+	unsigned char t;
+	SCC_ACCESS_INIT(port);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: setsignals dtr %d rts %d...\n", dtr, rts);
+#endif
+	local_irq_save(flags);
+	t = SCCread(TX_CTRL_REG);
+	if (dtr >= 0) t = dtr? (t | TCR_DTR): (t & ~TCR_DTR);
+	if (rts >= 0) t = rts? (t | TCR_RTS): (t & ~TCR_RTS);
+	SCCwrite(TX_CTRL_REG, t);
+	local_irq_restore(flags);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: setsignals done\n");
+#endif
+}
+
+
+static void scc_send_xchar(struct tty_struct *tty, char ch)
+{
+	struct scc_port *port = (struct scc_port *)tty->driver_data;
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: send_xchar ...\n");
+#endif
+	port->x_char = ch;
+	if (ch)
+		scc_enable_tx_interrupts(port);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: send_xchar done\n");
+#endif
+}
+
+
+/*---------------------------------------------------------------------------
+ * Driver entrypoints referenced from above
+ *--------------------------------------------------------------------------*/
+
+static int scc_open (struct tty_struct * tty, struct file * filp)
+{
+	int line = tty->index;
+	int retval;
+	struct scc_port *port = &scc_ports[line];
+	int i, channel = port->channel;
+	unsigned long	flags;
+
+	if (atari_SCC_init_done && line == 1)
+		return -ENODEV;
+
+	SCC_ACCESS_INIT(port);
+
+	static const struct {
+		unsigned reg, val;
+	} scc_init_tab[] = {
+		/* no parity, 1 stop bit, async, 1:16 */
+		{ AUX1_CTRL_REG, A1CR_PARITY_NONE|A1CR_MODE_ASYNC_1|A1CR_CLKMODE_x64 },
+		/* parity error is special cond, ints disabled, no DMA */
+		{ INT_AND_DMA_REG, IDR_PARERR_AS_SPCOND | IDR_RX_INT_DISAB },
+		/* Rx 8 bits/char, no auto enable, Rx off */
+		{ RX_CTRL_REG, RCR_CHSIZE_8 },
+		/* DTR off, Tx 8 bits/char, RTS off, Tx off */
+		{ TX_CTRL_REG, TCR_CHSIZE_8 },
+		/* special features off */
+		{ AUX2_CTRL_REG, 0 },
+		/* RTxC is XTAL, TRxC is input, both clocks = RTxC */
+		{ CLK_CTRL_REG, CCR_TRxCOUT_XTAL | CCR_TXCLK_RTxC | CCR_RXCLK_RTxC },
+		{ DPLL_CTRL_REG, 0 },
+		/* Start Rx */
+		{ RX_CTRL_REG, RCR_RX_ENAB | RCR_CHSIZE_8 },
+		/* Start Tx */
+		{ TX_CTRL_REG, TCR_TX_ENAB | TCR_RTS | TCR_DTR | TCR_CHSIZE_8 },
+		/* Ext/Stat ints: CTS, DCD, SYNC (DSR) */
+		{ INT_CTRL_REG, ICR_ENAB_DCD_INT | ICR_ENAB_CTS_INT | ICR_ENAB_SYNC_INT },
+		/* Reset Ext/Stat ints */
+		{ COMMAND_REG, CR_EXTSTAT_RESET },
+		/* ...again */
+		{ COMMAND_REG, CR_EXTSTAT_RESET },
+		/* Rx int always, TX int off, Ext/Stat int on */
+		{ INT_AND_DMA_REG, IDR_EXTSTAT_INT_ENAB |
+		  IDR_PARERR_AS_SPCOND | IDR_RX_INT_ALL }
+	};
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: open port ...\n");
+#endif
+	if (!(port->gs.flags & ASYNC_INITIALIZED)) {
+#if defined(DEBUG)
+		printk(KERN_INFO "SCC: init port ...\n");
+#endif
+		local_irq_save(flags);
+
+		SCCmod( MASTER_INT_CTRL, 0x3f,
+			channel == 0 ? MIC_CH_A_RESET : MIC_CH_B_RESET );
+		udelay(40); /* extra delay after a reset */
+
+	       	for (i=0; i<sizeof(scc_init_tab)/sizeof(*scc_init_tab); ++i)
+		       	SCCwrite(scc_init_tab[i].reg, scc_init_tab[i].val);
+
+
+		/* remember status register for detection of DCD and CTS changes */
+		scc_last_status_reg[channel] = SCCread(STATUS_REG);
+
+		port->c_dcd = 0;	/* Prevent initial 1->0 interrupt */
+		scc_setsignals (port, 1,1);
+		local_irq_restore(flags);
+#if defined(DEBUG)
+		printk(KERN_INFO "SCC: init port done!\n");
+#endif
+	}
+
+	tty->driver_data = port;
+	port->gs.tty = tty;
+	port->gs.count++;
+#if defined(DEBUG)
+	printk(KERN_WARNING "SCC: gs init port ...\n");
+#endif
+	retval = gs_init_port(&port->gs);
+	if (retval) {
+		port->gs.count--;
+		return retval;
+	}
+#if defined(DEBUG)
+	printk(KERN_WARNING "SCC: gs init port done!\n");
+#endif
+	port->gs.flags |= GS_ACTIVE;
+
+#if defined(DEBUG)
+	printk(KERN_WARNING "SCC: gs wait ready ...\n");
+#endif
+	retval = gs_block_til_ready(port, filp);
+#if defined(DEBUG)
+	printk(KERN_WARNING "SCC: gs wait ready done!\n");
+#endif
+	if (retval) {
+		port->gs.count--;
+		return retval;
+	}
+
+	port->c_dcd = scc_get_CD (port);
+
+#if defined(DEBUG)
+	printk(KERN_WARNING "SCC: enable rx ints ...\n");
+#endif
+	scc_enable_rx_interrupts(port);
+#if defined(DEBUG)
+	printk(KERN_WARNING "SCC: enable rx ints done!\n");
+
+	printk(KERN_INFO "SCC: open port done!\n");
+#endif
+	return 0;
+}
+
+
+static void scc_throttle (struct tty_struct * tty)
+{
+	struct scc_port *port = (struct scc_port *)tty->driver_data;
+	unsigned long	flags;
+	SCC_ACCESS_INIT(port);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: throttle ...\n");
+#endif
+	if (tty->termios->c_cflag & CRTSCTS) {
+		local_irq_save(flags);
+		SCCmod(TX_CTRL_REG, ~TCR_RTS, 0);
+		local_irq_restore(flags);
+	}
+	if (I_IXOFF(tty))
+		scc_send_xchar(tty, STOP_CHAR(tty));
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: throttle done!\n");
+#endif
+}
+
+
+static void scc_unthrottle (struct tty_struct * tty)
+{
+	struct scc_port *port = (struct scc_port *)tty->driver_data;
+	unsigned long	flags;
+	SCC_ACCESS_INIT(port);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: unthrottle ...\n");
+#endif
+	if (tty->termios->c_cflag & CRTSCTS) {
+		local_irq_save(flags);
+		SCCmod(TX_CTRL_REG, 0xff, TCR_RTS);
+		local_irq_restore(flags);
+	}
+	if (I_IXOFF(tty))
+		scc_send_xchar(tty, START_CHAR(tty));
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: unthrottle done!\n");
+#endif
+}
+
+
+static int scc_ioctl(struct tty_struct *tty, struct file *file,
+		     unsigned int cmd, unsigned long arg)
+{
+	struct scc_port *port = (struct scc_port *) tty->driver_data;
+	int retval;
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: ioctl! cmd %d, arg %p \n", cmd, arg);
+#endif
+	//if (serial_paranoia_check(info, tty->device, "zs_ioctl"))
+	//	return -ENODEV;
+
+	if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
+	    (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGWILD)  &&
+	    (cmd != TIOCSERSWILD) && (cmd != TIOCSERGSTRUCT)) {
+		if (tty->flags & (1 << TTY_IO_ERROR))
+		    return -EIO;
+	}
+
+	switch (cmd) {
+		case TCSBRK:	/* SVID version: non-zero arg --> no break */
+#if defined(DEBUG)
+			printk(KERN_INFO "SCC: ioctl TCSBRK\n");
+#endif
+			retval = tty_check_change(tty);
+			if (retval)
+				return retval;
+			tty_wait_until_sent(tty, 0);
+			//if (!arg)
+			//	send_break(info, HZ/4);	/* 1/4 second */
+			return 0;
+		case TCSBRKP:	/* support for POSIX tcsendbreak() */
+#if defined(DEBUG)
+			printk(KERN_INFO "SCC: ioctl TCSBRKP\n");
+#endif
+			retval = tty_check_change(tty);
+			if (retval)
+				return retval;
+			tty_wait_until_sent(tty, 0);
+			//send_break(info, arg ? arg*(HZ/10) : HZ/4);
+			return 0;
+		case TIOCGSOFTCAR:
+#if defined(DEBUG)
+			printk(KERN_INFO "SCC: ioctl TIOCGSOFTCAR\n");
+#endif
+			if (put_user(C_CLOCAL(tty) ? 1 : 0,
+				     (unsigned long *) arg))
+				return -EFAULT;
+			return 0;
+		case TIOCSSOFTCAR:
+#if defined(DEBUG)
+			printk(KERN_INFO "SCC: ioctl TIOCSSOFTCAR\n");
+#endif
+			if (get_user(arg, (unsigned long *) arg))
+				return -EFAULT;
+			tty->termios->c_cflag =
+				((tty->termios->c_cflag & ~CLOCAL) |
+				 (arg ? CLOCAL : 0));
+			return 0;
+		case TIOCMGET:
+#if defined(DEBUG)
+			printk(KERN_INFO "SCC: ioctl TIOCMGET\n");
+#endif
+			//return get_modem_info(info, (unsigned int *) arg);
+			return 0;
+		case TIOCMBIS:
+		case TIOCMBIC:
+		case TIOCMSET:
+#if defined(DEBUG)
+			printk(KERN_INFO "SCC: ioctl TIOCMSET\n");
+#endif
+			//return set_modem_info(info, cmd, (unsigned int *) arg);
+			return 0;
+		case TIOCGSERIAL:
+#if defined(DEBUG)
+			printk(KERN_INFO "SCC: ioctl TIOCGSERIAL\n");
+#endif
+			return 0;
+			//return get_serial_info(info,
+			//		       (struct serial_struct *) arg);
+		case TIOCSSERIAL:
+#if defined(DEBUG)
+			printk(KERN_INFO "SCC: ioctl TIOCSSERIAL\n");
+#endif
+			return 0;
+			//return set_serial_info(info,
+			//		       (struct serial_struct *) arg);
+		case TIOCSERGETLSR: /* Get line status register */
+#if defined(DEBUG)
+			printk(KERN_INFO "SCC: ioctl TIOCSERGETLSR\n");
+#endif
+			return 0;
+			//return get_lsr_info(info, (unsigned int *) arg);
+
+		case TIOCSERGSTRUCT:
+#if defined(DEBUG)
+			printk(KERN_INFO "SCC: ioctl TIOCSERGSTRUCT\n");
+#endif
+			return 0;
+			if (copy_to_user((struct scc_port *) arg,
+				    port, sizeof(struct scc_port)))
+				return -EFAULT;
+			return 0;
+
+		default:
+#if defined(DEBUG)
+			printk(KERN_INFO "SCC: ioctl default\n");
+#endif
+			return -ENOIOCTLCMD;
+		}
+	return 0;
+
+	return -ENOIOCTLCMD;
+}
+
+
+static void scc_break_ctl(struct tty_struct *tty, int break_state)
+{
+	struct scc_port *port = (struct scc_port *)tty->driver_data;
+	unsigned long	flags;
+	SCC_ACCESS_INIT(port);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: break ctl ...\n");
+#endif
+	local_irq_save(flags);
+	SCCmod(TX_CTRL_REG, ~TCR_SEND_BREAK, 
+			break_state ? TCR_SEND_BREAK : 0);
+	local_irq_restore(flags);
+#if defined(DEBUG)
+	printk(KERN_INFO "SCC: break ctl done!\n");
+#endif
+}
+
+
+/*---------------------------------------------------------------------------
+ * Serial console stuff...
+ *--------------------------------------------------------------------------*/
+#if 1
+#define scc_delay() \
+    do {			\
+	__asm__ __volatile__ ( "tstb %0" : : "g" (*scc_del) : "cc" );\
+    } while (0)
+
+#define SCC_WRITE(reg,val)				\
+    do {						\
+	scc.cha_b_ctrl = (reg);				\
+	scc_delay();					\
+	scc.cha_b_ctrl = (val);				\
+	scc_delay();					\
+    } while(0)
+
+/* loops_per_jiffy isn't initialized yet, so we can't use udelay(). This does a
+ * delay of ~ 60us. */
+#define LONG_DELAY()				\
+    do {					\
+	int i;					\
+	for( i = 100; i > 0; --i )		\
+	    scc_delay();				\
+    } while(0)
+
+static void atari_init_scc_port( int cflag )
+{
+    extern int atari_SCC_reset_done;
+    static int clksrc_table[9] =
+	/* reg 11: 0x50 = BRG, 0x00 = RTxC, 0x28 = TRxC */
+	{ 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x00, 0x00 };
+    static int brgsrc_table[9] =
+	/* reg 14: 0 = RTxC, 2 = PCLK */
+	{ 2, 2, 2, 2, 2, 2, 0, 2, 2 };
+    static int clkmode_table[9] =
+	/* reg 4: 0x40 = x16, 0x80 = x32, 0xc0 = x64 */
+	{ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0xc0, 0x80 };
+    static int div_table[9] =
+	/* reg12 (BRG low) */
+	{ 208, 138, 103, 50, 24, 11, 1, 0, 0 };
+
+    int baud = cflag & CBAUD;
+    int clksrc, clkmode, div, reg3, reg5;
+
+    scc_del = &mfp.par_dt_reg;
+
+    if (cflag & CBAUDEX)
+	baud += B38400;
+    if (baud < B1200 || baud > B38400+2)
+	baud = B9600; /* use default 9600bps for non-implemented rates */
+    baud -= B1200; /* tables starts at 1200bps */
+
+    clksrc  = clksrc_table[baud];
+    clkmode = clkmode_table[baud];
+    div     = div_table[baud];
+    if (ATARIHW_PRESENT(TT_MFP) && baud >= 6) {
+	/* special treatment for TT, where rates >= 38400 are done via TRxC */
+	clksrc = 0x28; /* TRxC */
+	clkmode = baud == 6 ? 0xc0 :
+		  baud == 7 ? 0x80 : /* really 76800bps */
+			      0x40;  /* really 153600bps */
+	div = 0;
+    }
+
+    reg3 = (cflag & CSIZE) == CS8 ? 0xc0 : 0x40;
+    reg5 = (cflag & CSIZE) == CS8 ? 0x60 : 0x20 | 0x82 /* assert DTR/RTS */;
+
+    (void)scc.cha_b_ctrl;	/* reset reg pointer */
+    SCC_WRITE( 9, 0xc0 );	/* reset */
+    LONG_DELAY();		/* extra delay after WR9 access */
+    SCC_WRITE( 4, (cflag & PARENB) ? ((cflag & PARODD) ? 0x01 : 0x03) : 0 |
+		  0x04 /* 1 stopbit */ |
+		  clkmode );
+    SCC_WRITE( 3, reg3 );
+    SCC_WRITE( 5, reg5 );
+    SCC_WRITE( 9, 0 );		/* no interrupts */
+    LONG_DELAY();		/* extra delay after WR9 access */
+    SCC_WRITE( 10, 0 );		/* NRZ mode */
+    SCC_WRITE( 11, clksrc );	/* main clock source */
+    SCC_WRITE( 12, div );	/* BRG value */
+    SCC_WRITE( 13, 0 );		/* BRG high byte */
+    SCC_WRITE( 14, brgsrc_table[baud] );
+    SCC_WRITE( 14, brgsrc_table[baud] | (div ? 1 : 0) );
+    SCC_WRITE( 3, reg3 | 1 );
+    SCC_WRITE( 5, reg5 | 8 );
+
+    atari_SCC_reset_done = 1;
+    atari_SCC_init_done = 1;
+}
+
+static void scc_ch_write (char ch)
+{
+	volatile char *p = NULL;
+	
+	if (MACH_IS_TT || MACH_IS_FALCON)
+		p = (volatile char *)&scc.cha_b_ctrl;
+
+	if (MACH_IS_ST)
+		p = (volatile char *)&scc.cha_b_ctrl;
+
+	if (MACH_IS_STE)
+		p = (volatile char *)&st_escc.cha_b_ctrl;
+
+	do {
+		scc_delay();
+	}
+	while (!(*p & 4));
+	// scc_delay();
+	// *p = 8;
+	scc_delay();
+	*(p+1) = ch;
+}
+
+/* The console must be locked when we get here. */
+
+static void scc_console_write (struct console *co, const char *str, unsigned count)
+{
+	unsigned long	flags;
+
+	//printk("scc_console_write: %s\n", str);
+	local_irq_save(flags);
+
+	while (count--)
+	{
+		if (*str == '\n')
+			scc_ch_write ('\r');
+		scc_ch_write (*str++);
+	}
+	local_irq_restore(flags);
+	//printk("scc_console_write done!\n");
+}
+
+static struct tty_driver *scc_console_device(struct console *c, int *index)
+{
+	*index = c->index;
+	return scc_driver;
+}
+
+
+static int __init scc_console_setup(struct console *co, char *options)
+{
+	printk("scc_console_setup: initializing SCC port B\n");
+	atari_init_scc_port(B9600|CS8);
+	printk("scc_console_setup: done!\n");
+	return 0;
+}
+
+
+static struct console sercons = {
+	.name		= "ttyS",
+	.write		= scc_console_write,
+	.device		= scc_console_device,
+	.setup		= scc_console_setup,
+	.flags		= CON_PRINTBUFFER,
+	.index		= -1,
+};
+
+
+static int __init vme_scc_console_init(void)
+{
+	if (MACH_IS_TT ||
+		MACH_IS_ST ||
+		MACH_IS_FALCON)
+		register_console(&sercons);
+	return 0;
+}
+
+console_initcall(vme_scc_console_init);
+#endif
+
+/***************************** End of Functions *********************/
+
+MODULE_AUTHOR("Michael Schmitz");
+MODULE_DESCRIPTION("Atari Amd8350 SCC serial driver");
+MODULE_LICENSE("GPL");
--- linux-2.6.19-m68k-cvs/drivers/char/Makefile	2006-12-02 00:46:01.000000000 +0100
+++ linux-2.6.19-m68k/drivers/char/Makefile	2006-12-09 14:28:33.000000000 +0100
@@ -29,6 +29,7 @@
 obj-$(CONFIG_SPECIALIX)		+= specialix.o
 obj-$(CONFIG_MOXA_INTELLIO)	+= moxa.o
 obj-$(CONFIG_A2232)		+= ser_a2232.o generic_serial.o
+obj-$(CONFIG_ATARI_SCC)		+= atari_scc.o generic_serial.o
 obj-$(CONFIG_ATARI_DSP56K)	+= dsp56k.o
 obj-$(CONFIG_MOXA_SMARTIO)	+= mxser.o
 obj-$(CONFIG_COMPUTONE)		+= ip2/

[Index of Archives]     [Video for Linux]     [Yosemite News]     [Linux S/390]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux