📄 zs.c
字号:
/* * decserial.c: Serial port driver for IOASIC DECsatations. * * Derived from drivers/macintosh/macserial.c by Harald Koerfgen. * Derived from drivers/sbus/char/sunserial.c by Paul Mackerras. * * DECstation changes * Copyright (C) 1998 Harald Koerfgen (Harald.Koerfgen@home.ivm.de) * * 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) * * Keyboard and mouse are not supported right now. If you want to change this, * you might want to have a look at drivers/sbus/char/sunserial.c to see * how this might be done. HK */#include <linux/config.h>#include <linux/errno.h>#include <linux/signal.h>#include <linux/sched.h>#include <linux/timer.h>#include <linux/interrupt.h>#include <linux/tty.h>#include <linux/tty_flip.h>#include <linux/major.h>#include <linux/string.h>#include <linux/fcntl.h>#include <linux/mm.h>#include <linux/kernel.h>#include <linux/delay.h>#include <linux/init.h>#ifdef CONFIG_SERIAL_CONSOLE#include <linux/console.h>#endif#include <asm/io.h>#include <asm/pgtable.h>#include <asm/irq.h>#include <asm/system.h>#include <asm/segment.h>#include <asm/bitops.h>#include <asm/uaccess.h>#include <asm/wbflush.h>#include <asm/dec/interrupts.h>#include <asm/dec/machtype.h>#include <asm/dec/tc.h>#include <asm/dec/ioasic_addrs.h>#ifdef CONFIG_KGDB#include <asm/kgdb.h>#endif#include "zs.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 */#define RECOVERY_DELAY udelay(2)struct dec_zschannel zs_channels[NUM_CHANNELS];struct dec_serial zs_soft[NUM_CHANNELS];int zs_channels_found;struct dec_serial *zs_chain; /* list of all channels */struct tty_struct zs_ttys[NUM_CHANNELS];#ifdef CONFIG_SERIAL_CONSOLEstatic struct console sercons;#endif#ifdef CONFIG_KGDBstruct dec_zschannel *zs_kgdbchan;static unsigned char scc_inittab[] = { 9, 0x80, /* reset A side (CHRA) */ 13, 0, /* set baud rate divisor */ 12, 1, 14, 1, /* baud rate gen enable, src=rtxc (BRENABL) */ 11, 0x50, /* clocks = br gen (RCBR | TCBR) */ 5, 0x6a, /* tx 8 bits, assert RTS (Tx8 | TxENAB | RTS) */ 4, 0x44, /* x16 clock, 1 stop (SB1 | X16CLK)*/ 3, 0xc1, /* rx enable, 8 bits (RxENABLE | Rx8)*/};#endifstatic unsigned char zs_init_regs[16] __initdata = { 0, /* write 0 */ 0, /* write 1 */ 0xf0, /* write 2 */ (Rx8), /* write 3 */ (X16CLK | SB1), /* write 4 */ (Tx8), /* write 5 */ 0, 0, 0, /* write 6, 7, 8 */ (VIS), /* write 9 */ (NRZ), /* write 10 */ (TCBR | RCBR), /* write 11 */ 0, 0, /* BRG time constant, write 12 + 13 */ (BRSRC | BRENABL), /* write 14 */ 0 /* write 15 */};#define ZS_CLOCK 7372800 /* Z8530 RTxC input clock rate */DECLARE_TASK_QUEUE(tq_zs_serial);struct tty_driver serial_driver, callout_driver;static int serial_refcount;/* serial subtype definitions */#define SERIAL_TYPE_NORMAL 1#define SERIAL_TYPE_CALLOUT 2/* number of characters left in xmit buffer before we ask for more */#define WAKEUP_CHARS 256/* * Debugging. */#undef SERIAL_DEBUG_INTR#undef SERIAL_DEBUG_OPEN#undef SERIAL_DEBUG_FLOW#undef SERIAL_DEBUG_THROTTLE#undef SERIAL_PARANOIA_CHECK#define RS_STROBE_TIME 10#define RS_ISR_PASS_LIMIT 256#define _INLINE_ inlinestatic void probe_sccs(void);static void change_speed(struct dec_serial *info);static void rs_wait_until_sent(struct tty_struct *tty, int timeout);static struct tty_struct *serial_table[NUM_CHANNELS];static struct termios *serial_termios[NUM_CHANNELS];static struct termios *serial_termios_locked[NUM_CHANNELS];#ifndef MIN#define MIN(a,b) ((a) < (b) ? (a) : (b))#endif/* * tmp_buf is used as a temporary buffer by serial_write. We need to * lock it in case the copy_from_user 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 DECLARE_MUTEX(tmp_buf_sem);static inline int serial_paranoia_check(struct dec_serial *info, dev_t device, const char *routine){#ifdef SERIAL_PARANOIA_CHECK static const char *badmagic = "Warning: bad magic number for serial struct (%d, %d) in %s\n"; static const char *badinfo = "Warning: null mac_serial for (%d, %d) in %s\n"; if (!info) { printk(badinfo, MAJOR(device), MINOR(device), routine); return 1; } if (info->magic != SERIAL_MAGIC) { printk(badmagic, MAJOR(device), MINOR(device), routine); return 1; }#endif return 0;}/* * 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, 0, 0 };/* * Reading and writing Z8530 registers. */static inline unsigned char read_zsreg(struct dec_zschannel *channel, unsigned char reg){ unsigned char retval; if (reg != 0) { *channel->control = reg & 0xf; wbflush(); RECOVERY_DELAY; } retval = *channel->control; RECOVERY_DELAY; return retval;}static inline void write_zsreg(struct dec_zschannel *channel, unsigned char reg, unsigned char value){ if (reg != 0) { *channel->control = reg & 0xf; wbflush(); RECOVERY_DELAY; } *channel->control = value; wbflush(); RECOVERY_DELAY; return;}static inline unsigned char read_zsdata(struct dec_zschannel *channel){ unsigned char retval; retval = *channel->data; RECOVERY_DELAY; return retval;}static inline void write_zsdata(struct dec_zschannel *channel, unsigned char value){ *channel->data = value; wbflush(); RECOVERY_DELAY; return;}static inline void load_zsregs(struct dec_zschannel *channel, unsigned char *regs){/* ZS_CLEARERR(channel); ZS_CLEARFIFO(channel); */ /* Load 'em up */ write_zsreg(channel, R4, regs[R4]); write_zsreg(channel, R3, regs[R3] & ~RxENABLE); write_zsreg(channel, R5, regs[R5] & ~TxENAB); write_zsreg(channel, R9, regs[R9]); write_zsreg(channel, R1, regs[R1]); write_zsreg(channel, R2, regs[R2]); write_zsreg(channel, R10, regs[R10]); 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 dec_serial *ss, int set){ if (ss->zs_channel != ss->zs_chan_a) { if (set) ss->zs_chan_a->curregs[5] |= (RTS | DTR); else ss->zs_chan_a->curregs[5] &= ~(RTS | DTR); write_zsreg(ss->zs_chan_a, 5, ss->zs_chan_a->curregs[5]); } return;}/* Utility routines for the Zilog */static inline int get_zsbaud(struct dec_serial *ss){ struct dec_zschannel *channel = ss->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->clk_divisor)));}/* On receive, this clears errors and the receiver interrupts */static inline void rs_recv_clear(struct dec_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 * ----------------------------------------------------------------------- *//* * This routine is used by the interrupt handler to schedule * processing in the software interrupt portion of the driver. */static _INLINE_ void rs_sched_event(struct dec_serial *info, int event){ info->event |= 1 << event; queue_task(&info->tqueue, &tq_zs_serial); mark_bh(SERIAL_BH);}static _INLINE_ void receive_chars(struct dec_serial *info, struct pt_regs *regs){ struct tty_struct *tty = info->tty; unsigned char ch, stat, flag; while ((read_zsreg(info->zs_channel, 0) & Rx_CH_AV) != 0) { stat = read_zsreg(info->zs_channel, R1); ch = read_zsdata(info->zs_channel);#ifdef CONFIG_KGDB if (info->kgdb_channel) { if (ch == 0x03 || ch == '$') breakpoint(); if (stat & (Rx_OVR|FRM_ERR|PAR_ERR)) write_zsreg(info->zs_channel, 0, ERR_RES); return; }#endif if (!tty) continue; if (tty->flip.count >= TTY_FLIPBUF_SIZE) { static int flip_buf_ovf; ++flip_buf_ovf; continue; } tty->flip.count++; { static int flip_max_cnt; if (flip_max_cnt < tty->flip.count) flip_max_cnt = tty->flip.count; } if (stat & Rx_OVR) { flag = TTY_OVERRUN; } else if (stat & FRM_ERR) { flag = TTY_FRAME; } else if (stat & PAR_ERR) { flag = TTY_PARITY; } else flag = 0; if (flag) /* reset the error indication */ write_zsreg(info->zs_channel, 0, ERR_RES); *tty->flip.flag_buf_ptr++ = flag; *tty->flip.char_buf_ptr++ = ch; } tty_flip_buffer_push(tty);}static void transmit_chars(struct dec_serial *info){ if ((read_zsreg(info->zs_channel, 0) & Tx_BUF_EMP) == 0) return; info->tx_active = 0; if (info->x_char) { /* Send next char */ write_zsdata(info->zs_channel, info->x_char); info->x_char = 0; info->tx_active = 1; return; } if ((info->xmit_cnt <= 0) || info->tty->stopped || info->tx_stopped) { write_zsreg(info->zs_channel, 0, RES_Tx_P); return; } /* Send char */ write_zsdata(info->zs_channel, info->xmit_buf[info->xmit_tail++]); info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1); info->xmit_cnt--; info->tx_active = 1; if (info->xmit_cnt < WAKEUP_CHARS) rs_sched_event(info, RS_EVENT_WRITE_WAKEUP);}static _INLINE_ void status_handle(struct dec_serial *info){ unsigned char status; /* Get status from Read Register 0 */ status = read_zsreg(info->zs_channel, 0); /* FIXEM: Check for DCD transitions */ if (((status ^ info->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->tx_stopped) { info->tx_stopped = 0; if (!info->tx_active) transmit_chars(info); } } else { info->tx_stopped = 1; } } /* Clear status condition... */ write_zsreg(info->zs_channel, 0, RES_EXT_INT); info->read_reg_zero = status;}/* * This is the serial driver's generic interrupt routine */void rs_interrupt(int irq, void *dev_id, struct pt_regs * regs){ struct dec_serial *info = (struct dec_serial *) 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) if (info->zs_chan_a == info->zs_channel) shift = 3; /* Channel A */ else shift = 0; /* Channel B */ for (;;) { zs_intreg = read_zsreg(info->zs_chan_a, 3) >> 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 rs_stop(struct tty_struct *tty){ struct dec_serial *info = (struct dec_serial *)tty->driver_data; unsigned long flags; if (serial_paranoia_check(info, tty->device, "rs_stop")) return; #if 1 save_flags(flags); cli(); if (info->zs_channel->curregs[5] & TxENAB) { info->zs_channel->curregs[5] &= ~TxENAB; write_zsreg(info->zs_channel, 5, info->zs_channel->curregs[5]); } restore_flags(flags);#endif}static void rs_start(struct tty_struct *tty){ struct dec_serial *info = (struct dec_serial *)tty->driver_data; unsigned long flags; if (serial_paranoia_check(info, tty->device, "rs_start")) return; save_flags(flags); cli();#if 1 if (info->xmit_cnt && info->xmit_buf && !(info->zs_channel->curregs[5] & TxENAB)) { info->zs_channel->curregs[5] |= TxENAB; write_zsreg(info->zs_channel, 5, info->zs_channel->curregs[5]); }#else if (info->xmit_cnt && info->xmit_buf && !info->tx_active) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -