summaryrefslogtreecommitdiffstats
path: root/drivers/char/mac_SCC.c
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>1998-08-25 09:12:35 +0000
committerRalf Baechle <ralf@linux-mips.org>1998-08-25 09:12:35 +0000
commitc7fc24dc4420057f103afe8fc64524ebc25c5d37 (patch)
tree3682407a599b8f9f03fc096298134cafba1c9b2f /drivers/char/mac_SCC.c
parent1d793fade8b063fde3cf275bf1a5c2d381292cd9 (diff)
o Merge with Linux 2.1.116.
o New Newport console code. o New G364 console code.
Diffstat (limited to 'drivers/char/mac_SCC.c')
-rw-r--r--drivers/char/mac_SCC.c1543
1 files changed, 1543 insertions, 0 deletions
diff --git a/drivers/char/mac_SCC.c b/drivers/char/mac_SCC.c
new file mode 100644
index 000000000..0295ab4b4
--- /dev/null
+++ b/drivers/char/mac_SCC.c
@@ -0,0 +1,1543 @@
+/*
+ * mac_SCC.c: m68k version of
+ *
+ * macserial.c: Serial port driver for Power Macintoshes.
+ * Extended for the 68K mac by Alan Cox.
+ * Rewritten to m68k serial design by Michael Schmitz
+ *
+ * Derived from drivers/sbus/char/sunserial.c by Paul Mackerras.
+ *
+ * Copyright (C) 1996 Paul Mackerras (Paul.Mackerras@cs.anu.edu.au)
+ * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu)
+ */
+
+/*
+ * Design note for the m68k rewrite:
+ * The structure of the m68k serial code requires separation of the low-level
+ * functions that talk directly to the hardware from the Linux serial driver
+ * code interfacing to the tty layer. The reason for this separation is simply
+ * the fact that the m68k serial hardware is, unlike the i386, based on a
+ * variety of chips, and the rs_* serial routines need to be shared.
+ *
+ * I've tried to make consistent use of the async_struct info populated in the
+ * midlevel code, and introduced an async_private struct to hold the Macintosh
+ * SCC internals (this was added to the async_struct for the PowerMac driver).
+ * Exception: the console and kgdb hooks still use the zs_soft[] data, and this
+ * is still filled in by the probe_sccs() routine, which provides some data
+ * for mac_SCC_init as well. Interrupts are registered in mac_SCC_init, so
+ * the console/kgdb stuff probably won't work before proper serial init, and
+ * I have to rely on keeping info and zs_soft consistent at least for the
+ * console/kgdb port.
+ *
+ * Update (16-11-97): The SCC interrupt handling was suffering from the problem
+ * that the autovector SCC interrupt was registered only once, hence only one
+ * async_struct was passed to the interrupt function and only interrupts from
+ * the corresponding channel could be handled (yes, major design flaw).
+ * The autovector interrupt is now registered by the main interrupt initfunc,
+ * and uses a handler that will call the registered SCC specific interrupts in
+ * turn. The SCC init has to register these as machspec interrupts now, as is
+ * done for the VIA interrupts elsewhere.
+ */
+
+#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/config.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+
+#include <asm/uaccess.h>
+#include <asm/setup.h>
+#include <asm/bootinfo.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/macints.h>
+#ifndef CONFIG_MAC
+#include <asm/prom.h>
+#endif
+#include <asm/system.h>
+#include <asm/segment.h>
+#include <asm/bitops.h>
+
+#include "mac_SCC.h"
+
+/*
+ * It would be nice to dynamically allocate everything that
+ * depends on NUM_SERIAL, so we could support any number of
+ * Z8530s, but for now...
+ */
+#define NUM_SERIAL 2 /* Max number of ZS chips supported */
+#define NUM_CHANNELS (NUM_SERIAL * 2) /* 2 channels per chip */
+
+#ifdef CONFIG_MAC
+/*
+ * All the Macintosh 68K boxes that have an MMU also have hardware
+ * recovery delays.
+ */
+#define RECOVERY_DELAY
+#else
+/* On PowerMacs, the hardware takes care of the SCC recovery time,
+ but we need the eieio to make sure that the accesses occur
+ in the order we want. */
+#define RECOVERY_DELAY eieio()
+#endif
+
+struct mac_zschannel *zs_kgdbchan;
+struct mac_zschannel zs_channels[NUM_CHANNELS];
+
+struct m68k_async_struct zs_soft[NUM_CHANNELS];
+struct m68k_async_private zs_soft_private[NUM_CHANNELS];
+int zs_channels_found;
+struct m68k_async_struct *zs_chain; /* list of all channels */
+
+struct tty_struct zs_ttys[NUM_CHANNELS];
+/** struct tty_struct *zs_constty; **/
+
+/* Console hooks... */
+static int zs_cons_chanout = 0;
+static int zs_cons_chanin = 0;
+struct m68k_async_struct *zs_consinfo = 0;
+struct mac_zschannel *zs_conschan;
+
+static unsigned char kgdb_regs[16] = {
+ 0, 0, 0, /* write 0, 1, 2 */
+ (Rx8 | RxENABLE), /* write 3 */
+ (X16CLK | SB1 | PAR_EVEN), /* write 4 */
+ (Tx8 | TxENAB), /* write 5 */
+ 0, 0, 0, /* write 6, 7, 8 */
+ (NV), /* write 9 */
+ (NRZ), /* write 10 */
+ (TCBR | RCBR), /* write 11 */
+ 1, 0, /* 38400 baud divisor, write 12 + 13 */
+ (BRENABL), /* write 14 */
+ (DCDIE) /* write 15 */
+};
+
+#define ZS_CLOCK 3686400 /* Z8530 RTxC input clock rate */
+
+/* Debugging... DEBUG_INTR is bad to use when one of the zs
+ * lines is your console ;(
+ */
+#undef SERIAL_DEBUG_INTR
+#undef SERIAL_DEBUG_OPEN
+#undef SERIAL_DEBUG_FLOW
+
+#define RS_STROBE_TIME 10
+#define RS_ISR_PASS_LIMIT 256
+
+#define _INLINE_ inline
+
+static void probe_sccs(void);
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+
+/***************************** Prototypes *****************************/
+
+static void SCC_init_port( struct m68k_async_struct *info, int type, int channel );
+#if 0
+#ifdef MODULE
+static void SCC_deinit_port( struct m68k_async_struct *info, int channel );
+#endif
+#endif
+
+/* FIXME !!! Currently, only autovector interrupt used! */
+#if 0
+static void SCC_rx_int (int irq, void *data, struct pt_regs *fp);
+static void SCC_spcond_int (int irq, void *data, struct pt_regs *fp);
+static void SCC_tx_int (int irq, void *data, struct pt_regs *fp);
+static void SCC_stat_int (int irq, void *data, struct pt_regs *fp);
+static void SCC_ri_int (int irq, void *data, struct pt_regs *fp);
+#endif
+
+static int SCC_check_open( struct m68k_async_struct *info, struct tty_struct
+ *tty, struct file *file );
+static void SCC_init( struct m68k_async_struct *info );
+static void SCC_deinit( struct m68k_async_struct *info, int leave_dtr );
+static void SCC_enab_tx_int( struct m68k_async_struct *info, int enab_flag );
+static int SCC_check_custom_divisor( struct m68k_async_struct *info, int baud_base,
+ int divisor );
+static void SCC_change_speed( struct m68k_async_struct *info );
+#if 0
+static int SCC_clocksrc( unsigned baud_base, unsigned channel );
+#endif
+static void SCC_throttle( struct m68k_async_struct *info, int status );
+static void SCC_set_break( struct m68k_async_struct *info, int break_flag );
+static void SCC_get_serial_info( struct m68k_async_struct *info, struct
+ serial_struct *retinfo );
+static unsigned int SCC_get_modem_info( struct m68k_async_struct *info );
+static int SCC_set_modem_info( struct m68k_async_struct *info, int new_dtr, int
+ new_rts );
+static int SCC_ioctl( struct tty_struct *tty, struct file *file, struct
+ m68k_async_struct *info, unsigned int cmd, unsigned long arg );
+static void SCC_stop_receive (struct m68k_async_struct *info);
+static int SCC_trans_empty (struct m68k_async_struct *info);
+
+/************************* End of Prototypes **************************/
+
+
+static SERIALSWITCH SCC_switch = {
+ SCC_init, SCC_deinit, SCC_enab_tx_int,
+ SCC_check_custom_divisor, SCC_change_speed,
+ SCC_throttle, SCC_set_break,
+ SCC_get_serial_info, SCC_get_modem_info,
+ SCC_set_modem_info, SCC_ioctl, SCC_stop_receive, SCC_trans_empty,
+ SCC_check_open
+};
+
+/*
+ * tmp_buf is used as a temporary buffer by serial_write. We need to
+ * lock it in case the memcpy_fromfs blocks while swapping in a page,
+ * and some other program tries to do a serial write at the same time.
+ * Since the lock will only come under contention when the system is
+ * swapping and available memory is low, it makes sense to share one
+ * buffer across all the serial ports, since it significantly saves
+ * memory if large numbers of serial ports are open.
+ */
+static unsigned char tmp_buf[4096]; /* This is cheating */
+static struct semaphore tmp_buf_sem = MUTEX;
+
+/*
+ * This is used to figure out the divisor speeds and the timeouts
+ */
+static int baud_table[] = {
+ 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+ 9600, 19200, 38400, 57600, 115200, 0 };
+
+/*
+ * Reading and writing Z8530 registers.
+ */
+static inline unsigned char read_zsreg(struct mac_zschannel *channel,
+ unsigned char reg)
+{
+ unsigned char retval;
+
+ if (reg != 0) {
+ *channel->control = reg;
+ RECOVERY_DELAY;
+ }
+ retval = *channel->control;
+ RECOVERY_DELAY;
+ return retval;
+}
+
+static inline void write_zsreg(struct mac_zschannel *channel,
+ unsigned char reg, unsigned char value)
+{
+ if (reg != 0) {
+ *channel->control = reg;
+ RECOVERY_DELAY;
+ }
+ *channel->control = value;
+ RECOVERY_DELAY;
+ return;
+}
+
+static inline unsigned char read_zsdata(struct mac_zschannel *channel)
+{
+ unsigned char retval;
+
+ retval = *channel->data;
+ RECOVERY_DELAY;
+ return retval;
+}
+
+static inline void write_zsdata(struct mac_zschannel *channel,
+ unsigned char value)
+{
+ *channel->data = value;
+ RECOVERY_DELAY;
+ return;
+}
+
+static inline void load_zsregs(struct mac_zschannel *channel,
+ unsigned char *regs)
+{
+ ZS_CLEARERR(channel);
+ ZS_CLEARFIFO(channel);
+ /* Load 'em up */
+ write_zsreg(channel, R4, regs[R4]);
+ write_zsreg(channel, R10, regs[R10]);
+ write_zsreg(channel, R3, regs[R3] & ~RxENABLE);
+ write_zsreg(channel, R5, regs[R5] & ~TxENAB);
+ write_zsreg(channel, R1, regs[R1]);
+ write_zsreg(channel, R9, regs[R9]);
+ write_zsreg(channel, R11, regs[R11]);
+ write_zsreg(channel, R12, regs[R12]);
+ write_zsreg(channel, R13, regs[R13]);
+ write_zsreg(channel, R14, regs[R14]);
+ write_zsreg(channel, R15, regs[R15]);
+ write_zsreg(channel, R3, regs[R3]);
+ write_zsreg(channel, R5, regs[R5]);
+ return;
+}
+
+/* Sets or clears DTR/RTS on the requested line */
+static inline void zs_rtsdtr(struct m68k_async_struct *ss, int set)
+{
+ if (set)
+ ss->private->curregs[5] |= (RTS | DTR);
+ else
+ ss->private->curregs[5] &= ~(RTS | DTR);
+ write_zsreg(ss->private->zs_channel, 5, ss->private->curregs[5]);
+ return;
+}
+
+static inline void kgdb_chaninit(struct m68k_async_struct *ss, int intson, int bps)
+{
+ int brg;
+
+ if (intson) {
+ kgdb_regs[R1] = INT_ALL_Rx;
+ kgdb_regs[R9] |= MIE;
+ } else {
+ kgdb_regs[R1] = 0;
+ kgdb_regs[R9] &= ~MIE;
+ }
+ brg = BPS_TO_BRG(bps, ZS_CLOCK/16);
+ kgdb_regs[R12] = brg;
+ kgdb_regs[R13] = brg >> 8;
+ load_zsregs(ss->private->zs_channel, kgdb_regs);
+}
+
+/* Utility routines for the Zilog */
+static inline int get_zsbaud(struct m68k_async_struct *ss)
+{
+ struct mac_zschannel *channel = ss->private->zs_channel;
+ int brg;
+
+ /* The baud rate is split up between two 8-bit registers in
+ * what is termed 'BRG time constant' format in my docs for
+ * the chip, it is a function of the clk rate the chip is
+ * receiving which happens to be constant.
+ */
+ brg = (read_zsreg(channel, 13) << 8);
+ brg |= read_zsreg(channel, 12);
+ return BRG_TO_BPS(brg, (ZS_CLOCK/(ss->private->clk_divisor)));
+}
+
+/* On receive, this clears errors and the receiver interrupts */
+static inline void SCC_recv_clear(struct mac_zschannel *zsc)
+{
+ write_zsreg(zsc, 0, ERR_RES);
+ write_zsreg(zsc, 0, RES_H_IUS); /* XXX this is unnecessary */
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * Here starts the interrupt handling routines. All of the following
+ * subroutines are declared as inline and are folded into
+ * rs_interrupt(). They were separated out for readability's sake.
+ *
+ * - Ted Ts'o (tytso@mit.edu), 7-Mar-93
+ * -----------------------------------------------------------------------
+ */
+
+extern void breakpoint(void); /* For the KGDB frame character */
+
+static /*_INLINE_*/ void receive_chars(struct m68k_async_struct *info,
+ struct pt_regs *regs)
+{
+ struct tty_struct *tty = info->tty;
+ unsigned char ch, stat, flag;
+
+ while ((read_zsreg(info->private->zs_channel, 0) & Rx_CH_AV) != 0) {
+
+ stat = read_zsreg(info->private->zs_channel, R1);
+ ch = read_zsdata(info->private->zs_channel);
+
+#ifdef SCC_DEBUG
+ printk("mac_SCC: receive_chars stat=%X char=%X \n", stat, ch);
+#endif
+
+#if 0 /* KGDB not yet supported */
+ /* Look for kgdb 'stop' character, consult the gdb documentation
+ * for remote target debugging and arch/sparc/kernel/sparc-stub.c
+ * to see how all this works.
+ */
+ if ((info->kgdb_channel) && (ch =='\003')) {
+ breakpoint();
+ continue;
+ }
+#endif
+
+ if (!tty)
+ continue;
+
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE)
+ tty_flip_buffer_push(tty);
+
+ if (stat & Rx_OVR) {
+ flag = TTY_OVERRUN;
+ /* reset the error indication */
+ write_zsreg(info->private->zs_channel, 0, ERR_RES);
+ } else if (stat & FRM_ERR) {
+ /* this error is not sticky */
+ flag = TTY_FRAME;
+ } else if (stat & PAR_ERR) {
+ flag = TTY_PARITY;
+ /* reset the error indication */
+ write_zsreg(info->private->zs_channel, 0, ERR_RES);
+ } else
+ flag = 0;
+
+ if (tty->flip.buf_num
+ && tty->flip.count >= TTY_FLIPBUF_SIZE) {
+#ifdef SCC_DEBUG_OVERRUN
+ printk("mac_SCC: flip buffer overrun!\n");
+#endif
+ return;
+ }
+
+ if (!tty->flip.buf_num
+ && tty->flip.count >= 2*TTY_FLIPBUF_SIZE) {
+ printk("mac_SCC: double flip buffer overrun!\n");
+ return;
+ }
+
+ tty->flip.count++;
+ *tty->flip.flag_buf_ptr++ = flag;
+ *tty->flip.char_buf_ptr++ = ch;
+ info->icount.rx++;
+ tty_flip_buffer_push(tty);
+ }
+#if 0
+clear_and_exit:
+ SCC_recv_clear(info->private->zs_channel);
+#endif
+}
+
+/* that's SCC_enable_tx_int, basically */
+
+static void transmit_chars(struct m68k_async_struct *info)
+{
+ if ((read_zsreg(info->private->zs_channel, 0) & Tx_BUF_EMP) == 0)
+ return;
+ info->private->tx_active = 0;
+
+ if (info->x_char) {
+ /* Send next char */
+ write_zsdata(info->private->zs_channel, info->x_char);
+ info->x_char = 0;
+ info->private->tx_active = 1;
+ return;
+ }
+
+ if ((info->xmit_cnt <= 0) || info->tty->stopped
+ || info->private->tx_stopped) {
+ write_zsreg(info->private->zs_channel, 0, RES_Tx_P);
+ return;
+ }
+
+ /* Send char */
+ write_zsdata(info->private->zs_channel, info->xmit_buf[info->xmit_tail++]);
+ info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1);
+ info->icount.tx++;
+ info->xmit_cnt--;
+ info->private->tx_active = 1;
+
+ if (info->xmit_cnt < WAKEUP_CHARS)
+ rs_sched_event(info, RS_EVENT_WRITE_WAKEUP);
+}
+
+static /*_INLINE_*/ void status_handle(struct m68k_async_struct *info)
+{
+ unsigned char status;
+
+ /* Get status from Read Register 0 */
+ status = read_zsreg(info->private->zs_channel, 0);
+
+ /* Check for DCD transitions */
+ if (((status ^ info->private->read_reg_zero) & DCD) != 0
+ && info->tty && C_CLOCAL(info->tty)) {
+ if (status & DCD) {
+ wake_up_interruptible(&info->open_wait);
+ } else if (!(info->flags & ZILOG_CALLOUT_ACTIVE)) {
+ if (info->tty)
+ tty_hangup(info->tty);
+ }
+ }
+
+ /* Check for CTS transitions */
+ if (info->tty && C_CRTSCTS(info->tty)) {
+ /*
+ * For some reason, on the Power Macintosh,
+ * it seems that the CTS bit is 1 when CTS is
+ * *negated* and 0 when it is asserted.
+ * The DCD bit doesn't seem to be inverted
+ * like this.
+ */
+ if ((status & CTS) == 0) {
+ if (info->private->tx_stopped) {
+ info->private->tx_stopped = 0;
+ if (!info->private->tx_active)
+ transmit_chars(info);
+ }
+ } else {
+ info->private->tx_stopped = 1;
+ }
+ }
+
+ /* Clear status condition... */
+ write_zsreg(info->private->zs_channel, 0, RES_EXT_INT);
+ info->private->read_reg_zero = status;
+}
+
+/*
+ * This is the serial driver's generic interrupt routine
+ */
+void mac_SCC_interrupt(int irq, void *dev_id, struct pt_regs * regs)
+{
+ struct m68k_async_struct *info = (struct m68k_async_struct *) dev_id;
+ unsigned char zs_intreg;
+ int shift;
+
+ /* NOTE: The read register 3, which holds the irq status,
+ * does so for both channels on each chip. Although
+ * the status value itself must be read from the A
+ * channel and is only valid when read from channel A.
+ * Yes... broken hardware...
+ */
+#define CHAN_IRQMASK (CHBRxIP | CHBTxIP | CHBEXT)
+
+#ifdef SCC_DEBUG
+ printk("mac_SCC: interrupt; port: %lx channel: %lx \n",
+ info->port, info->private->zs_channel);
+#endif
+
+ if (info->private->zs_chan_a == info->private->zs_channel)
+ shift = 3; /* Channel A */
+ else
+ shift = 0; /* Channel B */
+
+ for (;;) {
+ zs_intreg = read_zsreg(info->private->zs_chan_a, 3);
+#ifdef SCC_DEBUG
+ printk("mac_SCC: status %x shift %d shifted %x \n",
+ zs_intreg, shift, zs_intreg >> shift);
+#endif
+ zs_intreg = zs_intreg >> shift;
+ if ((zs_intreg & CHAN_IRQMASK) == 0)
+ break;
+
+ if (zs_intreg & CHBRxIP)
+ receive_chars(info, regs);
+ if (zs_intreg & CHBTxIP)
+ transmit_chars(info);
+ if (zs_intreg & CHBEXT)
+ status_handle(info);
+ }
+}
+
+/*
+ * -------------------------------------------------------------------
+ * Here ends the serial interrupt routines.
+ * -------------------------------------------------------------------
+ */
+
+/*
+ * ------------------------------------------------------------
+ * rs_stop() and rs_start()
+ *
+ * This routines are called before setting or resetting tty->stopped.
+ * ------------------------------------------------------------
+ */
+
+static void SCC_enab_tx_int( struct m68k_async_struct *info, int enab_flag )
+{
+ unsigned long flags;
+
+ if (enab_flag) {
+#if 0
+ save_flags(flags); cli();
+ if (info->private->curregs[5] & TxENAB) {
+ info->private->curregs[5] &= ~TxENAB;
+ info->private->pendregs[5] &= ~TxENAB;
+ write_zsreg(info->private->zs_channel, 5,
+ info->private->curregs[5]);
+ }
+ restore_flags(flags);
+#endif
+ /* FIXME: should call transmit_chars here ??? */
+ transmit_chars(info);
+ } else {
+ save_flags(flags); cli();
+#if 0
+ if ( info->xmit_cnt && info->xmit_buf &&
+ !(info->private->curregs[5] & TxENAB)) {
+ info->private->curregs[5] |= TxENAB;
+ info->private->pendregs[5] = info->private->curregs[5];
+ write_zsreg(info->private->zs_channel, 5,
+ info->private->curregs[5]);
+ }
+#else
+ if ( info->xmit_cnt && info->xmit_buf &&
+ !info->private->tx_active) {
+ transmit_chars(info);
+ }
+#endif
+ restore_flags(flags);
+ }
+
+}
+
+#if 0
+/*
+ * leftover from original driver ...
+ */
+static int SCC_startup(struct m68k_async_struct * info)
+{
+ unsigned long flags;
+
+ save_flags(flags); cli();
+
+#ifdef SERIAL_DEBUG_OPEN
+ printk("starting up ttyS%d (irq %d)...", info->line, info->irq);
+#endif
+
+ /*
+ * Clear the receive FIFO.
+ */
+ ZS_CLEARFIFO(info->private->zs_channel);
+ info->xmit_fifo_size = 1;
+
+ /*
+ * Clear the interrupt registers.
+ */
+ write_zsreg(info->private->zs_channel, 0, ERR_RES);
+ write_zsreg(info->private->zs_channel, 0, RES_H_IUS);
+
+ /*
+ * Turn on RTS and DTR.
+ */
+ zs_rtsdtr(info, 1);
+
+ /*
+ * Finally, enable sequencing and interrupts
+ */
+ info->private->curregs[1] = (info->private->curregs[1] & ~0x18)
+ | (EXT_INT_ENAB | INT_ALL_Rx | TxINT_ENAB);
+ info->private->pendregs[1] = info->private->curregs[1];
+ info->private->curregs[3] |= (RxENABLE | Rx8);
+ info->private->pendregs[3] = info->private->curregs[3];
+ info->private->curregs[5] |= (TxENAB | Tx8);
+ info->private->pendregs[5] = info->private->curregs[5];
+ info->private->curregs[9] |= (NV | MIE);
+ info->private->pendregs[9] = info->private->curregs[9];
+ write_zsreg(info->private->zs_channel, 3, info->private->curregs[3]);
+ write_zsreg(info->private->zs_channel, 5, info->private->curregs[5]);
+ write_zsreg(info->private->zs_channel, 9, info->private->curregs[9]);
+
+ /*
+ * Set the speed of the serial port
+ */
+ SCC_change_speed(info);
+
+ /* Save the current value of RR0 */
+ info->private->read_reg_zero = read_zsreg(info->private->zs_channel, 0);
+
+ restore_flags(flags);
+ return 0;
+}
+#endif
+
+/* FIXME: are these required ?? */
+static int SCC_check_open( struct m68k_async_struct *info, struct tty_struct *tty,
+ struct file *file )
+{
+ /* check on the basis of info->whatever ?? */
+ if (info->private->kgdb_channel || info->private->is_cons)
+ return -EBUSY;
+ return( 0 );
+}
+
+static void SCC_init( struct m68k_async_struct *info )
+{
+ /* FIXME: init currently done in probe_sccs() */
+
+ /* BUT: startup part needs to be done here! */
+
+#ifdef SCC_DEBUG
+ printk("mac_SCC: init, info %lx, info->port %lx \n", info, info->port);
+#endif
+ /*
+ * Clear the receive FIFO.
+ */
+ ZS_CLEARFIFO(info->private->zs_channel);
+ info->xmit_fifo_size = 1;
+
+ /*
+ * Clear the interrupt registers.
+ */
+ write_zsreg(info->private->zs_channel, 0, ERR_RES);
+ write_zsreg(info->private->zs_channel, 0, RES_H_IUS);
+
+ /*
+ * Turn on RTS and DTR.
+ */
+ zs_rtsdtr(info, 1);
+
+ /*
+ * Finally, enable sequencing and interrupts
+ */
+ info->private->curregs[1] = (info->private->curregs[1] & ~0x18)
+ | (EXT_INT_ENAB | INT_ALL_Rx | TxINT_ENAB);
+ info->private->pendregs[1] = info->private->curregs[1];
+ info->private->curregs[3] |= (RxENABLE | Rx8);
+ info->private->pendregs[3] = info->private->curregs[3];
+ info->private->curregs[5] |= (TxENAB | Tx8);
+ info->private->pendregs[5] = info->private->curregs[5];
+ info->private->curregs[9] |= (NV | MIE);
+ info->private->pendregs[9] = info->private->curregs[9];
+ write_zsreg(info->private->zs_channel, 3, info->private->curregs[3]);
+ write_zsreg(info->private->zs_channel, 5, info->private->curregs[5]);
+ write_zsreg(info->private->zs_channel, 9, info->private->curregs[9]);
+
+ /*
+ * Set the speed of the serial port - done in startup() !!
+ */
+#if 0
+ SCC_change_speed(info);
+#endif
+
+ /* Save the current value of RR0 */
+ info->private->read_reg_zero = read_zsreg(info->private->zs_channel, 0);
+
+}
+
+static void SCC_init_port( struct m68k_async_struct *info, int type, int channel )
+{
+ static int got_autovector = 0;
+
+#ifdef SCC_DEBUG
+ printk("mac_SCC: init_port, info %x \n", info);
+#endif
+ info->sw = &SCC_switch;
+ info->private = &zs_soft_private[channel];
+ info->private->zs_channel = &zs_channels[channel];
+ info->irq = IRQ4;
+ info->private->clk_divisor = 16;
+ info->private->zs_baud = get_zsbaud(info);
+ info->port = (int) info->private->zs_channel->control;
+
+ /*
+ * MSch: Extended interrupt scheme:
+ * The generic m68k interrupt code can't use multiple handlers for
+ * the same interrupt source (no chained interrupts).
+ * We have to plug in a 'master' interrupt handler instead, calling
+ * mac_SCC_interrupt with the proper arguments ...
+ */
+
+ if (!got_autovector) {
+ if(sys_request_irq(IRQ4, mac_SCC_handler, 0, "SCC master", info))
+ panic("macserial: can't get irq %d", IRQ4);
+#ifdef SCC_DEBUG
+ printk("mac_SCC: got SCC master interrupt %d, channel %d info %p\n",
+ IRQ4, channel, info);
+#endif
+ got_autovector = 1;
+ }
+
+ if (info->private->zs_chan_a == info->private->zs_channel) {
+ /* Channel A */
+ if (request_irq(IRQ_SCCA, mac_SCC_interrupt, 0, "SCC A", info))
+ panic("mac_SCC: can't get irq %d", IRQ_SCCA);
+#ifdef SCC_DEBUG
+ printk("mac_SCC: got SCC A interrupt %d, channel %d info %p\n",
+ IRQ_SCCA, channel, info);
+#endif
+ } else {
+ /* Channel B */
+ if (request_irq(IRQ_SCCB, mac_SCC_interrupt, 0, "SCC B", info))
+ panic("mac_SCC: can't get irq %d", IRQ_SCCB);
+#ifdef SCC_DEBUG
+ printk("mac_SCC: got SCC B interrupt %d, channel %d info %p\n",
+ IRQ_SCCB, channel, info);
+#endif
+ }
+
+ /* If console serial line, then enable interrupts. */
+ if (info->private->is_cons) {
+ printk("mac_SCC: console line %lx; enabling interrupt!\n", info);
+ write_zsreg(info->private->zs_channel, R1,
+ (EXT_INT_ENAB | INT_ALL_Rx | TxINT_ENAB));
+ write_zsreg(info->private->zs_channel, R9, (NV | MIE));
+ write_zsreg(info->private->zs_channel, R10, (NRZ));
+ write_zsreg(info->private->zs_channel, R3, (Rx8 | RxENABLE));
+ write_zsreg(info->private->zs_channel, R5, (Tx8 | TxENAB));
+ }
+ /* If this is the kgdb line, enable interrupts because we
+ * now want to receive the 'control-c' character from the
+ * client attached to us asynchronously.
+ */
+ if (info->private->kgdb_channel) {
+ printk("mac_SCC: kgdb line %lx; enabling interrupt!\n", info);
+ kgdb_chaninit(info, 1, info->private->zs_baud);
+ }
+ /* Report settings (in m68kserial.c) */
+#ifndef CONFIG_MAC
+ printk("ttyS%d at 0x%08x (irq = %d)", info->line,
+ info->port, info->irq);
+ printk(" is a Z8530 SCC\n");
+#endif
+
+}
+
+/*
+ * This routine will shutdown a serial port; interrupts are disabled, and
+ * DTR is dropped if the hangup on close termio flag is on.
+ */
+static void SCC_deinit(struct m68k_async_struct * info, int leave_dtr)
+{
+ unsigned long flags;
+
+ save_flags(flags); cli(); /* Disable interrupts */
+
+ info->private->pendregs[1] = info->private->curregs[1] = 0;
+ write_zsreg(info->private->zs_channel, 1, 0); /* no interrupts */
+
+ info->private->curregs[3] &= ~RxENABLE;
+ info->private->pendregs[3] = info->private->curregs[3];
+ write_zsreg(info->private->zs_channel, 3, info->private->curregs[3]);
+
+ info->private->curregs[5] &= ~TxENAB;
+
+ if (!leave_dtr)
+ info->private->curregs[5] &= ~(DTR | RTS);
+ else
+ info->private->curregs[5] &= ~(RTS);
+
+ info->private->pendregs[5] = info->private->curregs[5];
+ write_zsreg(info->private->zs_channel, 5, info->private->curregs[5]);
+
+ restore_flags(flags);
+}
+
+/* FIXME !!! */
+static int SCC_check_custom_divisor( struct m68k_async_struct *info,
+ int baud_base, int divisor )
+{
+ return 0;
+}
+
+/*
+ * This routine is called to set the UART divisor registers to match
+ * the specified baud rate for a serial port.
+ */
+static void SCC_change_speed(struct m68k_async_struct *info)
+{
+ unsigned short port;
+ unsigned cflag;
+ int i;
+ int brg;
+ unsigned long flags;
+
+ if (!info->tty || !info->tty->termios)
+ return;
+ cflag = info->tty->termios->c_cflag;
+ if (!(port = info->port))
+ return;
+ i = cflag & CBAUD;
+
+ if (i == 0 && !(info->flags & ASYNC_SPD_MASK)) {
+ /* speed == 0 -> drop DTR */
+ save_flags(flags);
+ cli();
+ info->private->curregs[5] &= ~(DTR | RTS);
+ write_zsreg(info->private->zs_channel, 5, info->private->curregs[5]);
+ restore_flags(flags);
+ return;
+ }
+
+
+ if (i & CBAUDEX) {
+ /* XXX CBAUDEX is not obeyed.
+ * It is impossible at a 32bits PPC. XXX??
+ * But we have to report this to user ... someday.
+ */
+ i = B9600;
+ }
+
+ save_flags(flags); cli();
+ info->private->zs_baud = baud_table[i];
+ info->private->clk_divisor = 16;
+
+ info->private->curregs[4] = X16CLK;
+ info->private->curregs[11] = TCBR | RCBR;
+ brg = BPS_TO_BRG(info->private->zs_baud,
+ ZS_CLOCK/info->private->clk_divisor);
+ info->private->curregs[12] = (brg & 255);
+ info->private->curregs[13] = ((brg >> 8) & 255);
+ info->private->curregs[14] = BRENABL;
+
+ /* byte size and parity */
+ info->private->curregs[3] &= ~RxNBITS_MASK;
+ info->private->curregs[5] &= ~TxNBITS_MASK;
+ switch (cflag & CSIZE) {
+ case CS5:
+ info->private->curregs[3] |= Rx5;
+ info->private->curregs[5] |= Tx5;
+ break;
+ case CS6:
+ info->private->curregs[3] |= Rx6;
+ info->private->curregs[5] |= Tx6;
+ break;
+ case CS7:
+ info->private->curregs[3] |= Rx7;
+ info->private->curregs[5] |= Tx7;
+ break;
+ case CS8:
+ default: /* defaults to 8 bits */
+ info->private->curregs[3] |= Rx8;
+ info->private->curregs[5] |= Tx8;
+ break;
+ }
+ info->private->pendregs[3] = info->private->curregs[3];
+ info->private->pendregs[5] = info->private->curregs[5];
+
+ info->private->curregs[4] &= ~(SB_MASK | PAR_ENA | PAR_EVEN);
+ if (cflag & CSTOPB) {
+ info->private->curregs[4] |= SB2;
+ } else {
+ info->private->curregs[4] |= SB1;
+ }
+ if (cflag & PARENB) {
+ info->private->curregs[4] |= PAR_ENA;
+ }
+ if (!(cflag & PARODD)) {
+ info->private->curregs[4] |= PAR_EVEN;
+ }
+ info->private->pendregs[4] = info->private->curregs[4];
+
+ info->private->curregs[15] &= ~(DCDIE | CTSIE);
+ if (!(cflag & CLOCAL)) {
+ info->private->curregs[15] |= DCDIE;
+ }
+ if (cflag & CRTSCTS) {
+ info->private->curregs[15] |= CTSIE;
+ if ((read_zsreg(info->private->zs_channel, 0) & CTS) != 0)
+ info->private->tx_stopped = 1;
+ } else
+ info->private->tx_stopped = 0;
+ info->private->pendregs[15] = info->private->curregs[15];
+
+ /* Load up the new values */
+ load_zsregs(info->private->zs_channel, info->private->curregs);
+
+ restore_flags(flags);
+}
+
+/* This is for console output over ttya/ttyb */
+static void SCC_put_char(char ch)
+{
+ struct mac_zschannel *chan = zs_conschan;
+ int loops = 0;
+ unsigned long flags;
+
+ if(!chan)
+ return;
+
+ save_flags(flags); cli();
+ while ((read_zsreg(chan, 0) & Tx_BUF_EMP) == 0 && loops < 10000) {
+ loops++;
+ udelay(5);
+ }
+ write_zsdata(chan, ch);
+ restore_flags(flags);
+}
+
+/* These are for receiving and sending characters under the kgdb
+ * source level kernel debugger.
+ */
+void putDebugChar(char kgdb_char)
+{
+ struct mac_zschannel *chan = zs_kgdbchan;
+
+ while ((read_zsreg(chan, 0) & Tx_BUF_EMP) == 0)
+ udelay(5);
+ write_zsdata(chan, kgdb_char);
+}
+
+char getDebugChar(void)
+{
+ struct mac_zschannel *chan = zs_kgdbchan;
+
+ while ((read_zsreg(chan, 0) & Rx_CH_AV) == 0)
+ udelay(5);
+ return read_zsdata(chan);
+}
+
+/*
+ * Fair output driver allows a process to speak.
+ */
+static void SCC_fair_output(void)
+{
+ int left; /* Output no more than that */
+ unsigned long flags;
+ struct m68k_async_struct *info = zs_consinfo;
+ char c;
+
+ if (info == 0) return;
+ if (info->xmit_buf == 0) return;
+
+ save_flags(flags); cli();
+ left = info->xmit_cnt;
+ while (left != 0) {
+ c = info->xmit_buf[info->xmit_tail];
+ info->xmit_tail = (info->xmit_tail+1) & (SERIAL_XMIT_SIZE-1);
+ info->xmit_cnt--;
+ restore_flags(flags);
+
+ SCC_put_char(c);
+
+ save_flags(flags); cli();
+ left = MIN(info->xmit_cnt, left-1);
+ }
+
+ restore_flags(flags);
+ return;
+}
+
+/*
+ * zs_console_print is registered for printk.
+ */
+static void zs_console_print(const char *p)
+{
+ char c;
+
+ while ((c = *(p++)) != 0) {
+ if (c == '\n')
+ SCC_put_char('\r');
+ SCC_put_char(c);
+ }
+
+ /* Comment this if you want to have a strict interrupt-driven output */
+ SCC_fair_output();
+}
+
+/* FIXME: check with SCC_enab_tx_int!! */
+#if 0
+static void rs_flush_chars(struct tty_struct *tty)
+{
+ struct m68k_async_struct *info = (struct m68k_async_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->device, "rs_flush_chars"))
+ return;
+
+ if (info->xmit_cnt <= 0 || tty->stopped || info->private->tx_stopped ||
+ !info->xmit_buf)
+ return;
+
+ /* Enable transmitter */
+ save_flags(flags); cli();
+ transmit_chars(info);
+ restore_flags(flags);
+}
+
+static int rs_write(struct tty_struct * tty, int from_user,
+ const unsigned char *buf, int count)
+{
+ int c, total = 0;
+ struct m68k_async_struct *info = (struct m68k_async_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->device, "rs_write"))
+ return 0;
+
+ if (!tty || !info->xmit_buf)
+ return 0;
+
+ save_flags(flags);
+ while (1) {
+ cli();
+ c = MIN(count, MIN(SERIAL_XMIT_SIZE - info->xmit_cnt - 1,
+ SERIAL_XMIT_SIZE - info->xmit_head));
+ if (c <= 0)
+ break;
+
+ if (from_user) {
+ down(&tmp_buf_sem);
+ memcpy_fromfs(tmp_buf, buf, c);
+ c = MIN(c, MIN(SERIAL_XMIT_SIZE - info->xmit_cnt - 1,
+ SERIAL_XMIT_SIZE - info->xmit_head));
+ memcpy(info->xmit_buf + info->xmit_head, tmp_buf, c);
+ up(&tmp_buf_sem);
+ } else
+ memcpy(info->xmit_buf + info->xmit_head, buf, c);
+ info->xmit_head = (info->xmit_head + c) & (SERIAL_XMIT_SIZE-1);
+ info->xmit_cnt += c;
+ restore_flags(flags);
+ buf += c;
+ count -= c;
+ total += c;
+ }
+ if (info->xmit_cnt && !tty->stopped && !info->tx_stopped
+ && !info->tx_active)
+ transmit_chars(info);
+ restore_flags(flags);
+ return total;
+}
+#endif
+
+/*
+ * ------------------------------------------------------------
+ * rs_throttle()
+ *
+ * This routine is called by the upper-layer tty layer to signal that
+ * incoming characters should be throttled.
+ * ------------------------------------------------------------
+ */
+static void SCC_throttle(struct m68k_async_struct *info, int status)
+{
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ if (status) {
+ /*
+ * Here we want to turn off the RTS line. On Macintoshes,
+ * we only get the DTR line, which goes to both DTR and
+ * RTS on the modem. RTS doesn't go out to the serial
+ * port socket. So you should make sure your modem is
+ * set to ignore DTR if you're using CRTSCTS.
+ */
+ info->private->curregs[5] &= ~(DTR | RTS);
+ info->private->pendregs[5] &= ~(DTR | RTS);
+ write_zsreg(info->private->zs_channel, 5,
+ info->private->curregs[5]);
+ } else {
+ /* Assert RTS and DTR lines */
+ info->private->curregs[5] |= DTR | RTS;
+ info->private->pendregs[5] |= DTR | RTS;
+ write_zsreg(info->private->zs_channel, 5,
+ info->private->curregs[5]);
+ }
+
+ restore_flags(flags);
+
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_ioctl() and friends
+ * ------------------------------------------------------------
+ */
+
+static void SCC_get_serial_info(struct m68k_async_struct * info,
+ struct serial_struct * retinfo)
+{
+ struct serial_struct tmp;
+
+ retinfo->baud_base = info->baud_base;
+ retinfo->custom_divisor = info->custom_divisor;
+}
+
+/* FIXME: set_serial_info needs check_custom_divisor !!! */
+
+/*
+ * get_lsr_info - get line status register info
+ *
+ * Purpose: Let user call ioctl() to get info when the UART physically
+ * is emptied. On bus types like RS485, the transmitter must
+ * release the bus after transmitting. This must be done when
+ * the transmit shift register is empty, not be done when the
+ * transmit holding register is empty. This functionality
+ * allows an RS485 driver to be written in user space.
+ */
+static int SCC_get_lsr_info(struct m68k_async_struct * info, unsigned int *value)
+{
+ unsigned char status;
+
+ cli();
+ status = read_zsreg(info->private->zs_channel, 0);
+ sti();
+ return status;
+}
+
+static unsigned int SCC_get_modem_info(struct m68k_async_struct *info)
+{
+ unsigned char control, status;
+ unsigned int result;
+
+ cli();
+ control = info->private->curregs[5];
+ status = read_zsreg(info->private->zs_channel, 0);
+ sti();
+ result = ((control & RTS) ? TIOCM_RTS: 0)
+ | ((control & DTR) ? TIOCM_DTR: 0)
+ | ((status & DCD) ? TIOCM_CAR: 0)
+ | ((status & CTS) ? 0: TIOCM_CTS);
+ return result;
+}
+
+/* FIXME: zs_setdtr was used in rs_open ... */
+
+static int SCC_set_modem_info(struct m68k_async_struct *info,
+ int new_dtr, int new_rts)
+{
+ int error;
+ unsigned int arg, bits;
+
+ bits = (new_rts ? RTS: 0) + (new_dtr ? DTR: 0);
+ info->private->curregs[5] = (info->private->curregs[5] & ~(DTR | RTS)) | bits;
+ info->private->pendregs[5] = info->private->curregs[5];
+ write_zsreg(info->private->zs_channel, 5, info->private->curregs[5]);
+ sti();
+ return 0;
+}
+
+/*
+ * This routine sends a break character out the serial port.
+ */
+static void SCC_set_break(struct m68k_async_struct * info, int break_flag)
+{
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ if (break_flag) {
+ info->private->curregs[5] |= SND_BRK;
+ write_zsreg(info->private->zs_channel, 5,
+ info->private->curregs[5]);
+ } else {
+ info->private->curregs[5] &= ~SND_BRK;
+ write_zsreg(info->private->zs_channel, 5,
+ info->private->curregs[5]);
+ }
+
+ restore_flags(flags);
+}
+
+/* FIXME: these have to be enabled in rs_ioctl !! */
+
+static int SCC_ioctl(struct tty_struct *tty, struct file * file,
+ struct m68k_async_struct * info, unsigned int cmd,
+ unsigned long arg)
+{
+ int error;
+ int retval;
+
+ switch (cmd) {
+ case TIOCSERGETLSR: /* Get line status register */
+ error = verify_area(VERIFY_WRITE, (void *) arg,
+ sizeof(unsigned int));
+ if (error)
+ return error;
+ else
+ return SCC_get_lsr_info(info, (unsigned int *) arg);
+
+ case TIOCSERGSTRUCT:
+ error = verify_area(VERIFY_WRITE, (void *) arg,
+ sizeof(struct m68k_async_struct));
+ if (error)
+ return error;
+ copy_to_user((struct m68k_async_struct *) arg,
+ info, sizeof(struct m68k_async_struct));
+ return 0;
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static void SCC_stop_receive (struct m68k_async_struct *info)
+{
+ /* disable Rx */
+ info->private->curregs[3] &= ~RxENABLE;
+ info->private->pendregs[3] = info->private->curregs[3];
+ write_zsreg(info->private->zs_channel, 3, info->private->curregs[3]);
+ /* disable Rx interrupts */
+ info->private->curregs[1] &= ~(0x18); /* disable any rx ints */
+ info->private->pendregs[1] = info->private->curregs[1];
+ write_zsreg(info->private->zs_channel, 1, info->private->curregs[1]);
+ ZS_CLEARFIFO(info->private->zs_channel);
+}
+
+static int SCC_trans_empty (struct m68k_async_struct *info)
+{
+ return (read_zsreg(info->private->zs_channel, 1) & ALL_SNT) != 0;
+}
+
+/* Finally, routines used to initialize the serial driver. */
+
+#ifdef CONFIG_MAC
+
+/*
+ * Mac: use boot_info data; assume 2 channels
+ */
+
+static void probe_sccs(void)
+{
+ int n;
+
+#define ZS_CONTROL 0x50F04000
+#define ZS_DATA (ZS_CONTROL+4)
+#define ZS_IRQ 5
+#define ZS_MOVE -2
+#define ZS_DATA_MOVE 4
+#define ZS_CH_A_FIRST 2
+
+ /* last-ditch fixup for NetBSD booter case */
+ if (mac_bi_data.sccbase == 0)
+ mac_bi_data.sccbase = ZS_CONTROL;
+
+ /* testing: fix up broken 24 bit addresses (ClassicII) */
+ if ((mac_bi_data.sccbase & 0x00FFFFFF) == mac_bi_data.sccbase)
+ mac_bi_data.sccbase |= 0x50000000;
+
+ for(n=0;n<2;n++)
+ {
+#if 0
+ zs_channels[n].control = (volatile unsigned char *)
+ ZS_CONTROL+ZS_MOVE*n;
+ zs_channels[n].data = (volatile unsigned char *)ZS_DATA+ZS_MOVE*n;
+#else
+ zs_channels[n].control = (volatile unsigned char *)
+ (mac_bi_data.sccbase+ZS_CH_A_FIRST)+ZS_MOVE*n;
+ zs_channels[n].data = (volatile unsigned char *)
+ (mac_bi_data.sccbase+ZS_CH_A_FIRST+ZS_DATA_MOVE)+ZS_MOVE*n;
+#endif
+ zs_soft[n].private = &zs_soft_private[n];
+ zs_soft[n].private->zs_channel = &zs_channels[n];
+ zs_soft[n].irq = IRQ4;
+#if 0
+ if (request_irq(ch->intrs[0], rs_interrupt, 0,
+ "SCC", &zs_soft[n]))
+ panic("macserial: can't get irq %d",
+ ch->intrs[0]);
+#endif
+ if (n & 1)
+ zs_soft[n].private->zs_chan_a = &zs_channels[n-1];
+ else
+ zs_soft[n].private->zs_chan_a = &zs_channels[n];
+ }
+
+ zs_channels_found=2;
+}
+
+#else
+
+/*
+ * PowerMAC - query the PROM
+ */
+
+static void show_serial_version(void)
+{
+ printk("PowerMac Z8530 serial driver version 1.00\n");
+}
+
+/* Ask the PROM how many Z8530s we have and initialize their zs_channels */
+static void
+probe_sccs()
+{
+ struct device_node *dev, *ch;
+ struct m68k_async_struct **pp;
+ int n;
+
+ n = 0;
+ pp = &zs_chain;
+ for (dev = find_devices("escc"); dev != 0; dev = dev->next) {
+ if (n >= NUM_CHANNELS) {
+ printk("Sorry, can't use %s: no more channels\n",
+ dev->full_name);
+ continue;
+ }
+ for (ch = dev->child; ch != 0; ch = ch->sibling) {
+ if (ch->n_addrs < 1 || ch ->n_intrs < 1) {
+ printk("Can't use %s: %d addrs %d intrs\n",
+ ch->full_name, ch->n_addrs, ch->n_intrs);
+ continue;
+ }
+ zs_channels[n].control = (volatile unsigned char *)
+ ch->addrs[0].address;
+ zs_channels[n].data = zs_channels[n].control
+ + ch->addrs[0].size / 2;
+ zs_soft[n].private = &zs_soft_private[n];
+ zs_soft[n].private->zs_channel = &zs_channels[n];
+ zs_soft[n].irq = ch->intrs[0];
+ if (request_irq(ch->intrs[0], mac_SCC_interrupt, 0,
+ "SCC", &zs_soft[n]))
+ panic("macserial: can't get irq %d",
+ ch->intrs[0]);
+ /* XXX this assumes the prom puts chan A before B */
+ if (n & 1)
+ zs_soft[n].private->zs_chan_a = &zs_channels[n-1];
+ else
+ zs_soft[n].private->zs_chan_a = &zs_channels[n];
+
+ *pp = &zs_soft[n];
+ pp = &zs_soft[n].private->zs_next;
+ ++n;
+ }
+ }
+ *pp = 0;
+ zs_channels_found = n;
+}
+
+#endif
+
+extern void register_console(void (*proc)(const char *));
+
+static inline void
+rs_cons_check(struct m68k_async_struct *ss, int channel)
+{
+ int i, o, io;
+ static consout_registered = 0;
+ static msg_printed = 0;
+
+ i = o = io = 0;
+
+ /* Is this one of the serial console lines? */
+ if ((zs_cons_chanout != channel) &&
+ (zs_cons_chanin != channel))
+ return;
+ zs_conschan = ss->private->zs_channel;
+ zs_consinfo = ss;
+
+ /* Register the console output putchar, if necessary */
+ if (zs_cons_chanout == channel) {
+ o = 1;
+ /* double whee.. */
+ if (!consout_registered) {
+ register_console(zs_console_print);
+ consout_registered = 1;
+ }
+ }
+
+ if (zs_cons_chanin == channel) {
+ i = 1;
+ }
+ if (o && i)
+ io = 1;
+ if (ss->private->zs_baud != 9600)
+ panic("Console baud rate weirdness");
+
+ /* Set flag variable for this port so that it cannot be
+ * opened for other uses by accident.
+ */
+ ss->private->is_cons = 1;
+
+ if (io) {
+ if(!msg_printed) {
+ printk("zs%d: console I/O\n", ((channel>>1)&1));
+ msg_printed = 1;
+ }
+ } else {
+ printk("zs%d: console %s\n", ((channel>>1)&1),
+ (i==1 ? "input" : (o==1 ? "output" : "WEIRD")));
+ }
+
+ /* FIXME : register interrupt here??? */
+}
+
+volatile int test_done;
+
+/* rs_init inits the driver */
+int mac_SCC_init(void)
+{
+ int channel, line, nr = 0, i;
+ unsigned long flags;
+ struct serial_struct req;
+ struct m68k_async_struct *info;
+
+ printk("Mac68K Z8530 serial driver version 1.01\n");
+
+ /* SCC present at all? */
+ if (MACH_IS_ATARI || MACH_IS_AMIGA
+#if 0
+ || !(MACHW_PRESENT(SCC) || MACHW_PRESENT(ST_ESCC))
+#endif
+ )
+ return( -ENODEV );
+
+ if (zs_chain == 0)
+ probe_sccs();
+
+ save_flags(flags);
+ cli();
+
+ /*
+ * FIXME: init of rs_table entry and register_serial now done,
+ * but possible clash of zs_soft[channel] and rs_table[channel]!!
+ * zs_soft initialized in probe_sccs(), some settings copied to
+ * info = &rs_table[channel], which is used by the mid-level code.
+ * The info->private part is shared among both!
+ */
+
+ for (channel = 0; channel < zs_channels_found; ++channel) {
+ req.line = channel;
+ req.type = SER_SCC_MAC;
+ req.port = zs_soft[channel].private->zs_channel->control;
+
+ if ((line = register_serial( &req )) >= 0) {
+ SCC_init_port( &rs_table[line], req.type, line );
+ ++nr;
+ }
+ else
+ printk(KERN_WARNING "Cannot allocate ttyS%d for SCC channel A\n", req.line );
+ }
+
+ restore_flags(flags);
+
+ return( nr > 0 ? 0 : -ENODEV );
+}
+
+/* Hooks for running a serial console. con_init() calls this if the
+ * console is being run over one of the serial ports.
+ * 'channel' is decoded as 0=modem 1=printer, 'chip' is ignored.
+ */
+void
+rs_cons_hook(int chip, int out, int channel)
+{
+ if (zs_chain == 0)
+ probe_sccs();
+ zs_soft[channel].private->clk_divisor = 16;
+ zs_soft[channel].private->zs_baud = get_zsbaud(&zs_soft[channel]);
+ rs_cons_check(&zs_soft[channel], channel);
+ if (out)
+ zs_cons_chanout = channel;
+ else
+ zs_cons_chanin = channel;
+
+ /* FIXME : register interrupt here??? */
+}
+
+/* This is called at boot time to prime the kgdb serial debugging
+ * serial line. The 'tty_num' argument is 0 for /dev/ttyS0 and 1
+ * for /dev/ttyS1 which is determined in setup_arch() from the
+ * boot command line flags.
+ */
+void
+rs_kgdb_hook(int tty_num)
+{
+ if (zs_chain == 0)
+ probe_sccs();
+ zs_kgdbchan = zs_soft[tty_num].private->zs_channel;
+ zs_soft[tty_num].private->clk_divisor = 16;
+ zs_soft[tty_num].private->zs_baud = get_zsbaud(&zs_soft[tty_num]);
+ zs_soft[tty_num].private->kgdb_channel = 1; /* This runs kgdb */
+ zs_soft[tty_num ^ 1].private->kgdb_channel = 0; /* This does not */
+ /* Turn on transmitter/receiver at 8-bits/char */
+ kgdb_chaninit(&zs_soft[tty_num], 0, 9600);
+ ZS_CLEARERR(zs_kgdbchan);
+ ZS_CLEARFIFO(zs_kgdbchan);
+
+ /* FIXME : register interrupt here??? */
+}
+