aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MAINTAINERS5
-rw-r--r--drivers/char/Kconfig33
-rw-r--r--drivers/char/decserial.c67
-rw-r--r--drivers/serial/Kconfig30
-rw-r--r--drivers/serial/Makefile1
-rw-r--r--drivers/serial/zs.c1287
-rw-r--r--drivers/serial/zs.h284
-rw-r--r--drivers/tc/Makefile1
-rw-r--r--drivers/tc/zs.c2203
-rw-r--r--drivers/tc/zs.h404
-rw-r--r--include/asm-mips/dec/serial.h36
-rw-r--r--include/linux/serial_core.h5
12 files changed, 1610 insertions, 2746 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 368a7181fa1..a9615a567da 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4110,6 +4110,11 @@ W: http://www.polyware.nl/~middelin/En/hobbies.html
W: http://www.polyware.nl/~middelin/hobbies.html
S: Maintained
+ZS DECSTATION Z85C30 SERIAL DRIVER
+P: Maciej W. Rozycki
+M: macro@linux-mips.org
+S: Maintained
+
THE REST
P: Linus Torvalds
S: Buried alive in reporters
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index d8d7125529c..97bd71bc3ae 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -372,39 +372,6 @@ config ISTALLION
To compile this driver as a module, choose M here: the
module will be called istallion.
-config SERIAL_DEC
- bool "DECstation serial support"
- depends on MACH_DECSTATION
- default y
- help
- This selects whether you want to be asked about drivers for
- DECstation serial ports.
-
- Note that the answer to this question won't directly affect the
- kernel: saying N will just cause the configurator to skip all
- the questions about DECstation serial ports.
-
-config SERIAL_DEC_CONSOLE
- bool "Support for console on a DECstation serial port"
- depends on SERIAL_DEC
- default y
- help
- If you say Y here, it will be possible to use a serial port as the
- system console (the system console is the device which receives all
- kernel messages and warnings and which allows logins in single user
- mode). Note that the firmware uses ttyS0 as the serial console on
- the Maxine and ttyS2 on the others.
-
- If unsure, say Y.
-
-config ZS
- bool "Z85C30 Serial Support"
- depends on SERIAL_DEC
- default y
- help
- Documentation on the Zilog 85C350 serial communications controller
- is downloadable at <http://www.zilog.com/pdfs/serial/z85c30.pdf>
-
config A2232
tristate "Commodore A2232 serial support (EXPERIMENTAL)"
depends on EXPERIMENTAL && ZORRO && BROKEN_ON_SMP
diff --git a/drivers/char/decserial.c b/drivers/char/decserial.c
deleted file mode 100644
index 8ea2bea2b18..00000000000
--- a/drivers/char/decserial.c
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * sercons.c
- * choose the right serial device at boot time
- *
- * triemer 6-SEP-1998
- * sercons.c is designed to allow the three different kinds
- * of serial devices under the decstation world to co-exist
- * in the same kernel. The idea here is to abstract
- * the pieces of the drivers that are common to this file
- * so that they do not clash at compile time and runtime.
- *
- * HK 16-SEP-1998 v0.002
- * removed the PROM console as this is not a real serial
- * device. Added support for PROM console in drivers/char/tty_io.c
- * instead. Although it may work to enable more than one
- * console device I strongly recommend to use only one.
- */
-
-#include <linux/init.h>
-#include <asm/dec/machtype.h>
-
-#ifdef CONFIG_ZS
-extern int zs_init(void);
-#endif
-
-#ifdef CONFIG_SERIAL_CONSOLE
-
-#ifdef CONFIG_ZS
-extern void zs_serial_console_init(void);
-#endif
-
-#endif
-
-/* rs_init - starts up the serial interface -
- handle normal case of starting up the serial interface */
-
-#ifdef CONFIG_SERIAL
-
-int __init rs_init(void)
-{
-#ifdef CONFIG_ZS
- if (IOASIC)
- return zs_init();
-#endif
- return -ENXIO;
-}
-
-__initcall(rs_init);
-
-#endif
-
-#ifdef CONFIG_SERIAL_CONSOLE
-
-/* serial_console_init handles the special case of starting
- * up the console on the serial port
- */
-static int __init decserial_console_init(void)
-{
-#ifdef CONFIG_ZS
- if (IOASIC)
- zs_serial_console_init();
-#endif
- return 0;
-}
-console_initcall(decserial_console_init);
-
-#endif
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 7fa413ddccf..18f62970644 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -486,6 +486,36 @@ config SERIAL_DZ_CONSOLE
If unsure, say Y.
+config SERIAL_ZS
+ tristate "DECstation Z85C30 serial support"
+ depends on MACH_DECSTATION
+ select SERIAL_CORE
+ default y
+ ---help---
+ Support for the Zilog 85C350 serial communications controller used
+ for serial ports in newer DECstation systems. These include the
+ DECsystem 5900 and all models of the DECstation and DECsystem 5000
+ systems except from model 200.
+
+ If unsure, say Y. To compile this driver as a module, choose M here:
+ the module will be called zs.
+
+config SERIAL_ZS_CONSOLE
+ bool "Support for console on a DECstation Z85C30 serial port"
+ depends on SERIAL_ZS=y
+ select SERIAL_CORE_CONSOLE
+ default y
+ ---help---
+ If you say Y here, it will be possible to use a serial port as the
+ system console (the system console is the device which receives all
+ kernel messages and warnings and which allows logins in single user
+ mode).
+
+ Note that the firmware uses ttyS1 as the serial console on the
+ Maxine and ttyS3 on the others using this driver.
+
+ If unsure, say Y.
+
config SERIAL_21285
tristate "DC21285 serial port support"
depends on ARM && FOOTBRIDGE
diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
index c48cdd61b73..af6377d480d 100644
--- a/drivers/serial/Makefile
+++ b/drivers/serial/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_V850E_UART) += v850e_uart.o
obj-$(CONFIG_SERIAL_PMACZILOG) += pmac_zilog.o
obj-$(CONFIG_SERIAL_LH7A40X) += serial_lh7a40x.o
obj-$(CONFIG_SERIAL_DZ) += dz.o
+obj-$(CONFIG_SERIAL_ZS) += zs.o
obj-$(CONFIG_SERIAL_SH_SCI) += sh-sci.o
obj-$(CONFIG_SERIAL_SGI_L1_CONSOLE) += sn_console.o
obj-$(CONFIG_SERIAL_CPM) += cpm_uart/
diff --git a/drivers/serial/zs.c b/drivers/serial/zs.c
new file mode 100644
index 00000000000..65f1294fd27
--- /dev/null
+++ b/drivers/serial/zs.c
@@ -0,0 +1,1287 @@
+/*
+ * zs.c: Serial port driver for IOASIC DECstations.
+ *
+ * Derived from drivers/sbus/char/sunserial.c by Paul Mackerras.
+ * Derived from drivers/macintosh/macserial.c by Harald Koerfgen.
+ *
+ * DECstation changes
+ * Copyright (C) 1998-2000 Harald Koerfgen
+ * Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2007 Maciej W. Rozycki
+ *
+ * For the rest of the code the original Copyright applies:
+ * Copyright (C) 1996 Paul Mackerras (Paul.Mackerras@cs.anu.edu.au)
+ * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu)
+ *
+ *
+ * Note: for IOASIC systems the wiring is as follows:
+ *
+ * mouse/keyboard:
+ * DIN-7 MJ-4 signal SCC
+ * 2 1 TxD <- A.TxD
+ * 3 4 RxD -> A.RxD
+ *
+ * EIA-232/EIA-423:
+ * DB-25 MMJ-6 signal SCC
+ * 2 2 TxD <- B.TxD
+ * 3 5 RxD -> B.RxD
+ * 4 RTS <- ~A.RTS
+ * 5 CTS -> ~B.CTS
+ * 6 6 DSR -> ~A.SYNC
+ * 8 CD -> ~B.DCD
+ * 12 DSRS(DCE) -> ~A.CTS (*)
+ * 15 TxC -> B.TxC
+ * 17 RxC -> B.RxC
+ * 20 1 DTR <- ~A.DTR
+ * 22 RI -> ~A.DCD
+ * 23 DSRS(DTE) <- ~B.RTS
+ *
+ * (*) EIA-232 defines the signal at this pin to be SCD, while DSRS(DCE)
+ * is shared with DSRS(DTE) at pin 23.
+ *
+ * As you can immediately notice the wiring of the RTS, DTR and DSR signals
+ * is a bit odd. This makes the handling of port B unnecessarily
+ * complicated and prevents the use of some automatic modes of operation.
+ */
+
+#if defined(CONFIG_SERIAL_ZS_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
+#define SUPPORT_SYSRQ
+#endif
+
+#include <linux/bug.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/irqflags.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/serial.h>
+#include <linux/serial_core.h>
+#include <linux/spinlock.h>
+#include <linux/sysrq.h>
+#include <linux/tty.h>
+#include <linux/types.h>
+
+#include <asm/atomic.h>
+#include <asm/system.h>
+
+#include <asm/dec/interrupts.h>
+#include <asm/dec/ioasic_addrs.h>
+#include <asm/dec/system.h>
+
+#include "zs.h"
+
+
+MODULE_AUTHOR("Maciej W. Rozycki <macro@linux-mips.org>");
+MODULE_DESCRIPTION("DECstation Z85C30 serial driver");
+MODULE_LICENSE("GPL");
+
+
+static char zs_name[] __initdata = "DECstation Z85C30 serial driver version ";
+static char zs_version[] __initdata = "0.10";
+
+/*
+ * It would be nice to dynamically allocate everything that
+ * depends on ZS_NUM_SCCS, so we could support any number of
+ * Z85C30s, but for now...
+ */
+#define ZS_NUM_SCCS 2 /* Max # of ZS chips supported. */
+#define ZS_NUM_CHAN 2 /* 2 channels per chip. */
+#define ZS_CHAN_A 0 /* Index of the channel A. */
+#define ZS_CHAN_B 1 /* Index of the channel B. */
+#define ZS_CHAN_IO_SIZE 8 /* IOMEM space size. */
+#define ZS_CHAN_IO_STRIDE 4 /* Register alignment. */
+#define ZS_CHAN_IO_OFFSET 1 /* The SCC resides on the high byte
+ of the 16-bit IOBUS. */
+#define ZS_CLOCK 7372800 /* Z85C30 PCLK input clock rate. */
+
+#define to_zport(uport) container_of(uport, struct zs_port, port)
+
+struct zs_parms {
+ resource_size_t scc[ZS_NUM_SCCS];
+ int irq[ZS_NUM_SCCS];
+};
+
+static struct zs_scc zs_sccs[ZS_NUM_SCCS];
+
+static u8 zs_init_regs[ZS_NUM_REGS] __initdata = {
+ 0, /* write 0 */
+ PAR_SPEC, /* write 1 */
+ 0, /* write 2 */
+ 0, /* write 3 */
+ X16CLK | SB1, /* write 4 */
+ 0, /* write 5 */
+ 0, 0, 0, /* write 6, 7, 8 */
+ MIE | DLC | NV, /* write 9 */
+ NRZ, /* write 10 */
+ TCBR | RCBR, /* write 11 */
+ 0, 0, /* BRG time constant, write 12 + 13 */
+ BRSRC | BRENABL, /* write 14 */
+ 0, /* write 15 */
+};
+
+/*
+ * Debugging.
+ */
+#undef ZS_DEBUG_REGS
+
+
+/*
+ * Reading and writing Z85C30 registers.
+ */
+static void recovery_delay(void)
+{
+ udelay(2);
+}
+
+static u8 read_zsreg(struct zs_port *zport, int reg)
+{
+ void __iomem *control = zport->port.membase + ZS_CHAN_IO_OFFSET;
+ u8 retval;
+
+ if (reg != 0) {
+ writeb(reg & 0xf, control);
+ fast_iob();
+ recovery_delay();
+ }
+ retval = readb(control);
+ recovery_delay();
+ return retval;
+}
+
+static void write_zsreg(struct zs_port *zport, int reg, u8 value)
+{
+ void __iomem *control = zport->port.membase + ZS_CHAN_IO_OFFSET;
+
+ if (reg != 0) {
+ writeb(reg & 0xf, control);
+ fast_iob(); recovery_delay();
+ }
+ writeb(value, control);
+ fast_iob();
+ recovery_delay();
+ return;
+}
+
+static u8 read_zsdata(struct zs_port *zport)
+{
+ void __iomem *data = zport->port.membase +
+ ZS_CHAN_IO_STRIDE + ZS_CHAN_IO_OFFSET;
+ u8 retval;
+
+ retval = readb(data);
+ recovery_delay();
+ return retval;
+}
+
+static void write_zsdata(struct zs_port *zport, u8 value)
+{
+ void __iomem *data = zport->port.membase +
+ ZS_CHAN_IO_STRIDE + ZS_CHAN_IO_OFFSET;
+
+ writeb(value, data);
+ fast_iob();
+ recovery_delay();
+ return;
+}
+
+#ifdef ZS_DEBUG_REGS
+void zs_dump(void)
+{
+ struct zs_port *zport;
+ int i, j;
+
+ for (i = 0; i < ZS_NUM_SCCS * ZS_NUM_CHAN; i++) {
+ zport = &zs_sccs[i / ZS_NUM_CHAN].zport[i % ZS_NUM_CHAN];
+
+ if (!zport->scc)
+ continue;
+
+ for (j = 0; j < 16; j++)
+ printk("W%-2d = 0x%02x\t", j, zport->regs[j]);
+ printk("\n");
+ for (j = 0; j < 16; j++)
+ printk("R%-2d = 0x%02x\t", j, read_zsreg(zport, j));
+ printk("\n\n");
+ }
+}
+#endif
+
+
+static void zs_spin_lock_cond_irq(spinlock_t *lock, int irq)
+{
+ if (irq)
+ spin_lock_irq(lock);
+ else
+ spin_lock(lock);
+}
+
+static void zs_spin_unlock_cond_irq(spinlock_t *lock, int irq)
+{
+ if (irq)
+ spin_unlock_irq(lock);
+ else
+ spin_unlock(lock);
+}
+
+static int zs_receive_drain(struct zs_port *zport)
+{
+ int loops = 10000;
+
+ while ((read_zsreg(zport, R0) & Rx_CH_AV) && loops--)
+ read_zsdata(zport);
+ return loops;
+}
+
+static int zs_transmit_drain(struct zs_port *zport, int irq)
+{
+ struct zs_scc *scc = zport->scc;
+ int loops = 10000;
+
+ while (!(read_zsreg(zport, R0) & Tx_BUF_EMP) && loops--) {
+ zs_spin_unlock_cond_irq(&scc->zlock, irq);
+ udelay(2);
+ zs_spin_lock_cond_irq(&scc->zlock, irq);
+ }
+ return loops;
+}
+
+static int zs_line_drain(struct zs_port *zport, int irq)
+{
+ struct zs_scc *scc = zport->scc;
+ int loops = 10000;
+
+ while (!(read_zsreg(zport, R1) & ALL_SNT) && loops--) {
+ zs_spin_unlock_cond_irq(&scc->zlock, irq);
+ udelay(2);
+ zs_spin_lock_cond_irq(&scc->zlock, irq);
+ }
+ return loops;
+}
+
+
+static void load_zsregs(struct zs_port *zport, u8 *regs, int irq)
+{
+ /* Let the current transmission finish. */
+ zs_line_drain(zport, irq);
+ /* Load 'em up. */
+ write_zsreg(zport, R3, regs[3] & ~RxENABLE);
+ write_zsreg(zport, R5, regs[5] & ~TxENAB);
+ write_zsreg(zport, R4, regs[4]);
+ write_zsreg(zport, R9, regs[9]);
+ write_zsreg(zport, R1, regs[1]);
+ write_zsreg(zport, R2, regs[2]);
+ write_zsreg(zport, R10, regs[10]);
+ write_zsreg(zport, R14, regs[14] & ~BRENABL);
+ write_zsreg(zport, R11, regs[11]);
+ write_zsreg(zport, R12, regs[12]);
+ write_zsreg(zport, R13, regs[13]);
+ write_zsreg(zport, R14, regs[14]);
+ write_zsreg(zport, R15, regs[15]);
+ if (regs[3] & RxENABLE)
+ write_zsreg(zport, R3, regs[3]);
+ if (regs[5] & TxENAB)
+ write_zsreg(zport, R5, regs[5]);
+ return;
+}
+
+
+/*
+ * Status handling routines.
+ */
+
+/*
+ * zs_tx_empty() -- get the transmitter empty status
+ *
+ * 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 unsigned int zs_tx_empty(struct uart_port *uport)
+{
+ struct zs_port *zport = to_zport(uport);
+ struct zs_scc *scc = zport->scc;
+ unsigned long flags;
+ u8 status;
+
+ spin_lock_irqsave(&scc->zlock, flags);
+ status = read_zsreg(zport, R1);
+ spin_unlock_irqrestore(&scc->zlock, flags);
+
+ return status & ALL_SNT ? TIOCSER_TEMT : 0;
+}
+
+static unsigned int zs_raw_get_ab_mctrl(struct zs_port *zport_a,
+ struct zs_port *zport_b)
+{
+ u8 status_a, status_b;
+ unsigned int mctrl;
+
+ status_a = read_zsreg(zport_a, R0);
+ status_b = read_zsreg(zport_b, R0);
+
+ mctrl = ((status_b & CTS) ? TIOCM_CTS : 0) |
+ ((status_b & DCD) ? TIOCM_CAR : 0) |
+ ((status_a & DCD) ? TIOCM_RNG : 0) |
+ ((status_a & SYNC_HUNT) ? TIOCM_DSR : 0);
+
+ return mctrl;
+}
+
+static unsigned int zs_raw_get_mctrl(struct zs_port *zport)
+{
+ struct zs_port *zport_a = &zport->scc->zport[ZS_CHAN_A];
+
+ return zport != zport_a ? zs_raw_get_ab_mctrl(zport_a, zport) : 0;
+}
+
+static unsigned int zs_raw_xor_mctrl(struct zs_port *zport)
+{
+ struct zs_port *zport_a = &zport->scc->zport[ZS_CHAN_A];
+ unsigned int mmask, mctrl, delta;
+ u8 mask_a, mask_b;
+
+ if (zport == zport_a)
+ return 0;
+
+ mask_a = zport_a->regs[15];
+ mask_b = zport->regs[15];
+
+ mmask = ((mask_b & CTSIE) ? TIOCM_CTS : 0) |
+ ((mask_b & DCDIE) ? TIOCM_CAR : 0) |
+ ((mask_a & DCDIE) ? TIOCM_RNG : 0) |
+ ((mask_a & SYNCIE) ? TIOCM_DSR : 0);
+
+ mctrl = zport->mctrl;
+ if (mmask) {
+ mctrl &= ~mmask;
+ mctrl |= zs_raw_get_ab_mctrl(zport_a, zport) & mmask;
+ }
+
+ delta = mctrl ^ zport->mctrl;
+ if (delta)
+ zport->mctrl = mctrl;
+
+ return delta;
+}
+
+static unsigned int zs_get_mctrl(struct uart_port *uport)
+{
+ struct zs_port *zport = to_zport(uport);
+ struct zs_scc *scc = zport->scc;
+ unsigned int mctrl;
+
+ spin_lock(&scc->zlock);
+ mctrl = zs_raw_get_mctrl(zport);
+ spin_unlock(&scc->zlock);
+
+ return mctrl;
+}
+
+static void zs_set_mctrl(struct uart_port *uport, unsigned int mctrl)
+{
+ struct zs_port *zport = to_zport(uport);
+ struct zs_scc *scc = zport->scc;
+ struct zs_port *zport_a = &scc->zport[ZS_CHAN_A];
+ u8 oldloop, newloop;
+
+ spin_lock(&scc->zlock);
+ if (zport != zport_a) {
+ if (mctrl & TIOCM_DTR)
+ zport_a->regs[5] |= DTR;
+ else
+ zport_a->regs[5] &= ~DTR;
+ if (mctrl & TIOCM_RTS)
+ zport_a->regs[5] |= RTS;
+ else
+ zport_a->regs[5] &= ~RTS;
+ write_zsreg(zport_a, R5, zport_a->regs[5]);
+ }
+
+ /* Rarely modified, so don't poke at hardware unless necessary. */
+ oldloop = zport->regs[14];
+ newloop = oldloop;
+ if (mctrl & TIOCM_LOOP)
+ newloop |= LOOPBAK;
+ else
+ newloop &= ~LOOPBAK;
+ if (newloop != oldloop) {
+ zport->regs[14] = newloop;
+ write_zsreg(zport, R14, zport->regs[14]);
+ }
+ spin_unlock(&scc->zlock);
+}
+
+static void zs_raw_stop_tx(struct zs_port *zport)
+{
+ write_zsreg(zport, R0, RES_Tx_P);
+ zport->tx_stopped = 1;
+}
+
+static void zs_stop_tx(struct uart_port *uport)
+{
+ struct zs_port *zport = to_zport(uport);
+ struct zs_scc *scc = zport->scc;
+
+ spin_lock(&scc->zlock);
+ zs_raw_stop_tx(zport);
+ spin_unlock(&scc->zlock);
+}
+
+static void zs_raw_transmit_chars(struct zs_port *);
+
+static void zs_start_tx(struct uart_port *uport)
+{
+ struct zs_port *zport = to_zport(uport);
+ struct zs_scc *scc = zport->scc;
+
+ spin_lock(&scc->zlock);
+ if (zport->tx_stopped) {
+ zs_transmit_drain(zport, 0);
+ zport->tx_stopped = 0;
+ zs_raw_transmit_chars(zport);
+ }
+ spin_unlock(&scc->zlock);
+}
+
+static void zs_stop_rx(struct uart_port *uport)
+{
+ struct zs_port *zport = to_zport(uport);
+ struct zs_scc *scc = zport->scc;
+ struct zs_port *zport_a = &scc->zport[ZS_CHAN_A];
+
+ spin_lock(&scc->zlock);
+ zport->regs[15] &= ~BRKIE;
+ zport->regs[1] &= ~(RxINT_MASK | TxINT_ENAB);
+ zport->regs[1] |= RxINT_DISAB;
+
+ if (zport != zport_a) {
+ /* A-side DCD tracks RI and SYNC tracks DSR. */
+ zport_a->regs[15] &= ~(DCDIE | SYNCIE);
+ write_zsreg(zport_a, R15, zport_a->regs[15]);
+ if (!(zport_a->regs[15] & BRKIE)) {
+ zport_a->regs[1] &= ~EXT_INT_ENAB;
+ write_zsreg(zport_a, R1, zport_a->regs[1]);
+ }
+
+ /* This-side DCD tracks DCD and CTS tracks CTS. */
+ zport->regs[15] &= ~(DCDIE | CTSIE);
+ zport->regs[1] &= ~EXT_INT_ENAB;
+ } else {
+ /* DCD tracks RI and SYNC tracks DSR for the B side. */
+ if (!(zport->regs[15] & (DCDIE | SYNCIE)))
+ zport->regs[1] &= ~EXT_INT_ENAB;
+ }
+
+ write_zsreg(zport, R15, zport->regs[15]);
+ write_zsreg(zport, R1, zport->regs[1]);
+ spin_unlock(&scc->zlock);
+}
+
+static void zs_enable_ms(struct uart_port *uport)
+{
+ struct zs_port *zport = to_zport(uport);
+ struct zs_scc *scc = zport->scc;
+ struct zs_port *zport_a = &scc->zport[ZS_CHAN_A];
+
+ if (zport == zport_a)
+ return;
+
+ spin_lock(&scc->zlock);
+
+ /* Clear Ext interrupts if not being handled already. */
+ if (!(zport_a->regs[1] & EXT_INT_ENAB))
+ write_zsreg(zport_a, R0, RES_EXT_INT);
+
+ /* A-side DCD tracks RI and SYNC tracks DSR. */
+ zport_a->regs[1] |= EXT_INT_ENAB;
+ zport_a->regs[15] |= DCDIE | SYNCIE;
+
+ /* This-side DCD tracks DCD and CTS tracks CTS. */
+ zport->regs[15] |= DCDIE | CTSIE;
+
+ zs_raw_xor_mctrl(zport);
+
+ write_zsreg(zport_a, R1, zport_a->regs[1]);
+ write_zsreg(zport_a, R15, zport_a->regs[15]);
+ write_zsreg(zport, R15, zport->regs[15]);
+ spin_unlock(&scc->zlock);
+}
+
+static void zs_break_ctl(struct uart_port *uport, int break_state)
+{
+ struct zs_port *zport = to_zport(uport);
+ struct zs_scc *scc = zport->scc;
+ unsigned long flags;
+
+ spin_lock_irqsave(&scc->zlock, flags);
+ if (break_state == -1)
+ zport->regs[5] |= SND_BRK;
+ else
+ zport->regs[5] &= ~SND_BRK;
+ write_zsreg(zport, R5, zport->regs[5]);
+ spin_unlock_irqrestore(&scc->zlock, flags);
+}
+
+
+/*
+ * Interrupt handling routines.
+ */
+#define Rx_BRK 0x0100 /* BREAK event software flag. */
+#define Rx_SYS 0x0200 /* SysRq event software flag. */
+
+static void zs_receive_chars(struct zs_port *zport)
+{
+ struct uart_port *uport = &zport->port;
+ struct zs_scc *scc = zport->scc;
+ struct uart_icount *icount;
+ unsigned int avail, status, ch, flag;
+ int count;
+
+ for (count = 16; count; count--) {
+ spin_lock(&scc->zlock);
+ avail = read_zsreg(zport, R0) & Rx_CH_AV;
+ spin_unlock(&scc->zlock);
+ if (!avail)
+ break;
+
+ spin_lock(&scc->zlock);
+ status = read_zsreg(zport, R1) & (Rx_OVR | FRM_ERR | PAR_ERR);
+ ch = read_zsdata(zport);
+ spin_unlock(&scc->zlock);
+
+ flag = TTY_NORMAL;
+
+ icount = &uport->icount;
+ icount->rx++;
+
+ /* Handle the null char got when BREAK is removed. */
+ if (!ch)
+ status |= zport->tty_break;
+ if (unlikely(status &
+ (Rx_OVR | FRM_ERR | PAR_ERR | Rx_SYS | Rx_BRK))) {
+ zport->tty_break = 0;
+
+ /* Reset the error indication. */
+ if (status & (Rx_OVR | FRM_ERR | PAR_ERR)) {
+ spin_lock(&scc->zlock);
+ write_zsreg(zport, R0, ERR_RES);
+ spin_unlock(&scc->zlock);
+ }
+
+ if (status & (Rx_SYS | Rx_BRK)) {
+ icount->brk++;
+ /* SysRq discards the null char. */
+ if (status & Rx_SYS)
+ continue;
+ } else if (status & FRM_ERR)
+ icount->frame++;
+ else if (status & PAR_ERR)
+ icount->parity++;
+ if (status & Rx_OVR)
+ icount->overrun++;
+
+ status &= uport->read_status_mask;
+ if (status & Rx_BRK)
+ flag = TTY_BREAK;
+ else if (status & FRM_ERR)
+ flag = TTY_FRAME;
+ else if (status & PAR_ERR)
+ flag = TTY_PARITY;
+ }
+
+ if (uart_handle_sysrq_char(uport, ch))
+ continue;
+
+ uart_insert_char(uport, status, Rx_OVR, ch, flag);
+ }
+
+ tty_flip_buffer_push(uport->info->tty);
+}
+
+static void zs_raw_transmit_chars(struct zs_port *zport)
+{
+ struct circ_buf *xmit = &zport->port.info->xmit;
+
+ /* XON/XOFF chars. */
+ if (zport->port.x_char) {
+ write_zsdata(zport, zport->port.x_char);
+ zport->port.icount.tx++;
+ zport->port.x_char = 0;
+ return;
+ }
+
+ /* If nothing to do or stopped or hardware stopped. */
+ if (uart_circ_empty(xmit) || uart_tx_stopped(&zport->port)) {
+ zs_raw_stop_tx(zport);
+ return;
+ }
+
+ /* Send char. */
+ write_zsdata(zport, xmit->buf[xmit->tail]);
+ xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+ zport->port.icount.tx++;
+
+ if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+ uart_write_wakeup(&zport->port);
+
+ /* Are we are done? */
+ if (uart_circ_empty(xmit))
+ zs_raw_stop_tx(zport);
+}
+
+static void zs_transmit_chars(struct zs_port *zport)
+{
+ struct zs_scc *scc = zport->scc;
+
+ spin_lock(&scc->zlock);
+ zs_raw_transmit_chars(zport);
+ spin_unlock(&scc->zlock);
+}
+
+static void zs_status_handle(struct zs_port *zport, struct zs_port *zport_a)
+{
+ struct uart_port *uport = &zport->port;
+ struct zs_scc *scc = zport->scc;
+ unsigned int delta;
+ u8 status, brk;
+
+ spin_lock(&scc->zlock);
+
+ /* Get status from Read Register 0. */
+ status = read_zsreg(zport, R0);
+
+ if (zport->regs[15] & BRKIE) {
+ brk = status & BRK_ABRT;
+ if (brk && !zport->brk) {
+ spin_unlock(&scc->zlock);
+ if (uart_handle_break(uport))
+ zport->tty_break = Rx_SYS;
+ else
+ zport->tty_break = Rx_BRK;
+ spin_lock(&scc->zlock);
+ }
+ zport->brk = brk;
+ }
+
+ if (zport != zport_a) {
+ delta = zs_raw_xor_mctrl(zport);
+ spin_unlock(&scc->zlock);
+
+ if (delta & TIOCM_CTS)
+ uart_handle_cts_change(uport,
+ zport->mctrl & TIOCM_CTS);
+ if (delta & TIOCM_CAR)
+ uart_handle_dcd_change(uport,
+ zport->mctrl & TIOCM_CAR);
+ if (delta & TIOCM_RNG)
+ uport->icount.dsr++;
+ if (delta & TIOCM_DSR)
+ uport->icount.rng++;
+
+ if (delta)
+ wake_up_interruptible(&uport->info->delta_msr_wait);
+
+ spin_lock(&scc->zlock);
+ }
+
+ /* Clear the status condition... */
+ write_zsreg(zport, R0, RES_EXT_INT);
+
+ spin_unlock(&scc->zlock);
+}
+
+/*
+ * This is the Z85C30 driver's generic interrupt routine.
+ */
+static irqreturn_t zs_interrupt(int irq, void *dev_id)
+{
+ struct zs_scc *scc = dev_id;
+ struct zs_port *zport_a = &scc->zport[ZS_CHAN_A];
+ struct zs_port *zport_b = &scc->zport[ZS_CHAN_B];
+ irqreturn_t status = IRQ_NONE;
+ u8 zs_intreg;
+ int count;
+
+ /*
+ * 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...
+ */
+ for (count = 16; count; count--) {
+ spin_lock(&scc->zlock);
+ zs_intreg = read_zsreg(zport_a, R3);
+ spin_unlock(&scc->zlock);
+ if (!zs_intreg)
+ break;
+
+ /*
+ * We do not like losing characters, so we prioritise
+ * interrupt sources a little bit differently than
+ * the SCC would, was it allowed to.
+ */
+ if (zs_intreg & CHBRxIP)
+ zs_receive_chars(zport_b);
+ if (zs_intreg & CHARxIP)
+ zs_receive_chars(zport_a);
+ if (zs_intreg & CHBEXT)
+ zs_status_handle(zport_b, zport_a);
+ if (zs_intreg & CHAEXT)
+ zs_status_handle(zport_a, zport_a);
+ if (zs_intreg & CHBTxIP)
+ zs_transmit_chars(zport_b);
+ if (zs_intreg & CHATxIP)
+ zs_transmit_chars(zport_a);
+
+ status = IRQ_HANDLED;
+ }
+
+ return status;
+}
+
+
+/*
+ * Finally, routines used to initialize the serial port.
+ */
+static int zs_startup(struct uart_port *uport)
+{
+ struct zs_port *zport = to_zport(uport);
+ struct zs_scc *scc = zport->scc;
+ unsigned long flags;
+ int irq_guard;
+ int ret;
+
+ irq_guard = atomic_add_return(1, &scc->irq_guard);
+ if (irq_guard == 1) {
+ ret = request_irq(zport->port.irq, zs_interrupt,
+ IRQF_SHARED, "scc", scc);
+ if (ret) {
+ atomic_add(-1, &scc->irq_guard);
+ printk(KERN_ERR "zs: can't get irq %d\n",
+ zport->port.irq);
+ return ret;
+ }
+ }
+
+ spin_lock_irqsave(&scc->zlock, flags);
+
+ /* Clear the receive FIFO. */
+ zs_receive_drain(zport);
+
+ /* Clear the interrupt registers. */
+ write_zsreg(zport, R0, ERR_RES);
+ write_zsreg(zport, R0, RES_Tx_P);
+ /* But Ext only if not being handled already. */
+ if (!(zport->regs[1] & EXT_INT_ENAB))
+ write_zsreg(zport, R0, RES_EXT_INT);
+
+ /* Finally, enable sequencing and interrupts. */
+ zport->regs[1] &= ~RxINT_MASK;
+ zport->regs[1] |= RxINT_ALL | TxINT_ENAB | EXT_INT_ENAB;
+ zport->regs[3] |= RxENABLE;
+ zport->regs[5] |= TxENAB;
+ zport->regs[15] |= BRKIE;
+ write_zsreg(zport, R1, zport->regs[1]);
+ write_zsreg(zport, R3, zport->regs[3]);
+ write_zsreg(zport, R5, zport->regs[5]);
+ write_zsreg(zport, R15, zport->regs[15]);
+
+ /* Record the current state of RR0. */
+ zport->mctrl = zs_raw_get_mctrl(zport);
+ zport->brk = read_zsreg(zport, R0) & BRK_ABRT;
+
+ zport->tx_stopped = 1;
+
+ spin_unlock_irqrestore(&scc->zlock, flags);
+
+ return 0;
+}
+
+static void zs_shutdown(struct uart_port *uport)
+{
+ struct zs_port *zport = to_zport(uport);
+ struct zs_scc *scc = zport->scc;
+ unsigned long flags;
+ int irq_guard;
+
+ spin_lock_irqsave(&scc->zlock, flags);
+
+ zport->regs[5] &= ~TxENAB;
+ zport->regs[3] &= ~RxENABLE;
+ write_zsreg(zport, R5, zport->regs[5]);
+ write_zsreg(zport, R3, zport->regs[3]);
+
+ spin_unlock_irqrestore(&scc->zlock, flags);
+
+ irq_guard = atomic_add_return(-1, &scc->irq_guard);
+ if (!irq_guard)
+ free_irq(zport->port.irq, scc);
+}
+
+
+static void zs_reset(struct zs_port *zport)
+{
+ struct zs_scc *scc = zport->scc;
+ int irq;
+ unsigned long flags;
+
+ spin_lock_irqsave(&scc->zlock, flags);
+ irq = !irqs_disabled_flags(flags);
+ if (!scc->initialised) {
+ /* Reset the pointer first, just in case... */
+ read_zsreg(zport, R0);
+ /* And let the current transmission finish. */
+ zs_line_drain(zport, irq);
+ write_zsreg(zport, R9, FHWRES);
+ udelay(10);
+ write_zsreg(zport, R9, 0);
+ scc->initialised = 1;
+ }
+ load_zsregs(zport, zport->regs, irq);
+ spin_unlock_irqrestore(&scc->zlock, flags);
+}
+
+static void zs_set_termios(struct uart_port *uport, struct ktermios *termios,
+ struct ktermios *old_termios)
+{
+ struct zs_port *zport = to_zport(uport);
+ struct zs_scc *scc = zport->scc;
+ struct zs_port *zport_a = &scc->zport[ZS_CHAN_A];
+ int irq;
+ unsigned int baud, brg;
+ unsigned long flags;
+
+ spin_lock_irqsave(&scc->zlock, flags);
+ irq = !irqs_disabled_flags(flags);
+
+ /* Byte size. */
+ zport->regs[3] &= ~RxNBITS_MASK;
+ zport->regs[5] &= ~TxNBITS_MASK;
+ switch (termios->c_cflag & CSIZE) {
+ case CS5:
+ zport->regs[3] |= Rx5;
+ zport->regs[5] |= Tx5;
+ break;
+ case CS6:
+ zport->regs[3] |= Rx6;
+ zport->regs[5] |= Tx6;
+ break;
+ case CS7:
+ zport->regs[3] |= Rx7;
+ zport->regs[5] |= Tx7;
+ break;
+ case CS8:
+ default:
+ zport->regs[3] |= Rx8;
+ zport->regs[5] |= Tx8;
+ break;
+ }
+
+ /* Parity and stop bits. */
+ zport->regs[4] &= ~(XCLK_MASK | SB_MASK | PAR_ENA | PAR_EVEN);
+ if (termios->c_cflag & CSTOPB)
+ zport->regs[4] |= SB2;
+ else
+ zport->regs[4] |= SB1;
+ if (termios->c_cflag & PARENB)
+ zport->regs[4] |= PAR_ENA;
+ if (!(termios->c_cflag & PARODD))
+ zport->regs[4] |= PAR_EVEN;
+ switch (zport->clk_mode) {
+ case 64:
+ zport->regs[4] |= X64CLK;
+ break;
+ case 32:
+ zport->regs[4] |= X32CLK;
+ break;
+ case 16:
+ zport->regs[4] |= X16CLK;
+ break;
+ case 1:
+ zport->regs[4] |= X1CLK;
+ break;
+ default:
+ BUG();
+ }
+
+ baud = uart_get_baud_rate(uport, termios, old_termios, 0,
+ uport->uartclk / zport->clk_mode / 4);
+
+ brg = ZS_BPS_TO_BRG(baud, uport->uartclk / zport->clk_mode);
+ zport->regs[12] = brg & 0xff;
+ zport->regs[13] = (brg >> 8) & 0xff;
+
+ uart_update_timeout(uport, termios->c_cflag, baud);
+
+ uport->read_status_mask = Rx_OVR;
+ if (termios->c_iflag & INPCK)
+ uport->read_status_mask |= FRM_ERR | PAR_ERR;
+ if (termios->c_iflag & (BRKINT | PARMRK))
+ uport->read_status_mask |= Rx_BRK;
+
+ uport->ignore_status_mask = 0;
+ if (termios->c_iflag & IGNPAR)
+ uport->ignore_status_mask |= FRM_ERR | PAR_ERR;
+ if (termios->c_iflag & IGNBRK) {