📄 mac_scc.c
字号:
/* * 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 <asm/hwtest.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()#endifstruct 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_ inlinestatic 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 MODULEstatic void SCC_deinit_port( struct m68k_async_struct *info, int channel );#endif#endif/* FIXME !!! Currently, only autovector interrupt used! */#if 0static 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);#endifstatic 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 0static int SCC_clocksrc( unsigned baud_base, unsigned channel );#endifstatic 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};/* * 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 0clear_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 */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -