sunzilog.c
来自「Linux Kernel 2.6.9 for OMAP1710」· C语言 代码 · 共 1,771 行 · 第 1/3 页
C
1,771 行
/* * sunzilog.c * * Driver for Zilog serial chips found on Sun workstations and * servers. This driver could actually be made more generic. * * This is based on the old drivers/sbus/char/zs.c code. A lot * of code has been simply moved over directly from there but * much has been rewritten. Credits therefore go out to Eddie * C. Dost, Pete Zaitcev, Ted Ts'o and Alex Buell for their * work there. * * Copyright (C) 2002 David S. Miller (davem@redhat.com) */#include <linux/config.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/sched.h>#include <linux/errno.h>#include <linux/delay.h>#include <linux/tty.h>#include <linux/tty_flip.h>#include <linux/major.h>#include <linux/string.h>#include <linux/ptrace.h>#include <linux/ioport.h>#include <linux/slab.h>#include <linux/circ_buf.h>#include <linux/serial.h>#include <linux/sysrq.h>#include <linux/console.h>#include <linux/spinlock.h>#ifdef CONFIG_SERIO#include <linux/serio.h>#endif#include <linux/init.h>#include <asm/io.h>#include <asm/irq.h>#ifdef CONFIG_SPARC64#include <asm/fhc.h>#endif#include <asm/sbus.h>#if defined(CONFIG_SERIAL_SUNZILOG_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)#define SUPPORT_SYSRQ#endif#include <linux/serial_core.h>#include "suncore.h"#include "sunzilog.h"/* On 32-bit sparcs we need to delay after register accesses * to accommodate sun4 systems, but we do not need to flush writes. * On 64-bit sparc we only need to flush single writes to ensure * completion. */#ifndef CONFIG_SPARC64#define ZSDELAY() udelay(5)#define ZSDELAY_LONG() udelay(20)#define ZS_WSYNC(channel) do { } while (0)#else#define ZSDELAY()#define ZSDELAY_LONG()#define ZS_WSYNC(__channel) \ sbus_readb(&((__channel)->control))#endifstatic int num_sunzilog;#define NUM_SUNZILOG num_sunzilog#define NUM_CHANNELS (NUM_SUNZILOG * 2)#define KEYBOARD_LINE 0x2#define MOUSE_LINE 0x3#define ZS_CLOCK 4915200 /* Zilog input clock rate. */#define ZS_CLOCK_DIVISOR 16 /* Divisor this driver uses. *//* * We wrap our port structure around the generic uart_port. */struct uart_sunzilog_port { struct uart_port port; /* IRQ servicing chain. */ struct uart_sunzilog_port *next; /* Current values of Zilog write registers. */ unsigned char curregs[NUM_ZSREGS]; unsigned int flags;#define SUNZILOG_FLAG_CONS_KEYB 0x00000001#define SUNZILOG_FLAG_CONS_MOUSE 0x00000002#define SUNZILOG_FLAG_IS_CONS 0x00000004#define SUNZILOG_FLAG_IS_KGDB 0x00000008#define SUNZILOG_FLAG_MODEM_STATUS 0x00000010#define SUNZILOG_FLAG_IS_CHANNEL_A 0x00000020#define SUNZILOG_FLAG_REGS_HELD 0x00000040#define SUNZILOG_FLAG_TX_STOPPED 0x00000080#define SUNZILOG_FLAG_TX_ACTIVE 0x00000100 unsigned int cflag; unsigned char parity_mask; unsigned char prev_status;#ifdef CONFIG_SERIO struct serio *serio; int serio_open;#endif};#define ZILOG_CHANNEL_FROM_PORT(PORT) ((struct zilog_channel *)((PORT)->membase))#define UART_ZILOG(PORT) ((struct uart_sunzilog_port *)(PORT))#define ZS_IS_KEYB(UP) ((UP)->flags & SUNZILOG_FLAG_CONS_KEYB)#define ZS_IS_MOUSE(UP) ((UP)->flags & SUNZILOG_FLAG_CONS_MOUSE)#define ZS_IS_CONS(UP) ((UP)->flags & SUNZILOG_FLAG_IS_CONS)#define ZS_IS_KGDB(UP) ((UP)->flags & SUNZILOG_FLAG_IS_KGDB)#define ZS_WANTS_MODEM_STATUS(UP) ((UP)->flags & SUNZILOG_FLAG_MODEM_STATUS)#define ZS_IS_CHANNEL_A(UP) ((UP)->flags & SUNZILOG_FLAG_IS_CHANNEL_A)#define ZS_REGS_HELD(UP) ((UP)->flags & SUNZILOG_FLAG_REGS_HELD)#define ZS_TX_STOPPED(UP) ((UP)->flags & SUNZILOG_FLAG_TX_STOPPED)#define ZS_TX_ACTIVE(UP) ((UP)->flags & SUNZILOG_FLAG_TX_ACTIVE)/* Reading and writing Zilog8530 registers. The delays are to make this * driver work on the Sun4 which needs a settling delay after each chip * register access, other machines handle this in hardware via auxiliary * flip-flops which implement the settle time we do in software. * * The port lock must be held and local IRQs must be disabled * when {read,write}_zsreg is invoked. */static unsigned char read_zsreg(struct zilog_channel *channel, unsigned char reg){ unsigned char retval; sbus_writeb(reg, &channel->control); ZSDELAY(); retval = sbus_readb(&channel->control); ZSDELAY(); return retval;}static void write_zsreg(struct zilog_channel *channel, unsigned char reg, unsigned char value){ sbus_writeb(reg, &channel->control); ZSDELAY(); sbus_writeb(value, &channel->control); ZSDELAY();}static void sunzilog_clear_fifo(struct zilog_channel *channel){ int i; for (i = 0; i < 32; i++) { unsigned char regval; regval = sbus_readb(&channel->control); ZSDELAY(); if (regval & Rx_CH_AV) break; regval = read_zsreg(channel, R1); sbus_readb(&channel->data); ZSDELAY(); if (regval & (PAR_ERR | Rx_OVR | CRC_ERR)) { sbus_writeb(ERR_RES, &channel->control); ZSDELAY(); ZS_WSYNC(channel); } }}/* This function must only be called when the TX is not busy. The UART * port lock must be held and local interrupts disabled. */static void __load_zsregs(struct zilog_channel *channel, unsigned char *regs){ int i; /* Let pending transmits finish. */ for (i = 0; i < 1000; i++) { unsigned char stat = read_zsreg(channel, R1); if (stat & ALL_SNT) break; udelay(100); } sbus_writeb(ERR_RES, &channel->control); ZSDELAY(); ZS_WSYNC(channel); sunzilog_clear_fifo(channel); /* Disable all interrupts. */ write_zsreg(channel, R1, regs[R1] & ~(RxINT_MASK | TxINT_ENAB | EXT_INT_ENAB)); /* Set parity, sync config, stop bits, and clock divisor. */ write_zsreg(channel, R4, regs[R4]); /* Set misc. TX/RX control bits. */ write_zsreg(channel, R10, regs[R10]); /* Set TX/RX controls sans the enable bits. */ write_zsreg(channel, R3, regs[R3] & ~RxENAB); write_zsreg(channel, R5, regs[R5] & ~TxENAB); /* Synchronous mode config. */ write_zsreg(channel, R6, regs[R6]); write_zsreg(channel, R7, regs[R7]); /* Don't mess with the interrupt vector (R2, unused by us) and * master interrupt control (R9). We make sure this is setup * properly at probe time then never touch it again. */ /* Disable baud generator. */ write_zsreg(channel, R14, regs[R14] & ~BRENAB); /* Clock mode control. */ write_zsreg(channel, R11, regs[R11]); /* Lower and upper byte of baud rate generator divisor. */ write_zsreg(channel, R12, regs[R12]); write_zsreg(channel, R13, regs[R13]); /* Now rewrite R14, with BRENAB (if set). */ write_zsreg(channel, R14, regs[R14]); /* External status interrupt control. */ write_zsreg(channel, R15, regs[R15]); /* Reset external status interrupts. */ write_zsreg(channel, R0, RES_EXT_INT); write_zsreg(channel, R0, RES_EXT_INT); /* Rewrite R3/R5, this time without enables masked. */ write_zsreg(channel, R3, regs[R3]); write_zsreg(channel, R5, regs[R5]); /* Rewrite R1, this time without IRQ enabled masked. */ write_zsreg(channel, R1, regs[R1]);}/* Reprogram the Zilog channel HW registers with the copies found in the * software state struct. If the transmitter is busy, we defer this update * until the next TX complete interrupt. Else, we do it right now. * * The UART port lock must be held and local interrupts disabled. */static void sunzilog_maybe_update_regs(struct uart_sunzilog_port *up, struct zilog_channel *channel){ if (!ZS_REGS_HELD(up)) { if (ZS_TX_ACTIVE(up)) { up->flags |= SUNZILOG_FLAG_REGS_HELD; } else { __load_zsregs(channel, up->curregs); } }}static void sunzilog_change_mouse_baud(struct uart_sunzilog_port *up){ unsigned int cur_cflag = up->cflag; int brg, new_baud; up->cflag &= ~CBAUD; up->cflag |= suncore_mouse_baud_cflag_next(cur_cflag, &new_baud); brg = BPS_TO_BRG(new_baud, ZS_CLOCK / ZS_CLOCK_DIVISOR); up->curregs[R12] = (brg & 0xff); up->curregs[R13] = (brg >> 8) & 0xff; sunzilog_maybe_update_regs(up, ZILOG_CHANNEL_FROM_PORT(&up->port));}static void sunzilog_kbdms_receive_chars(struct uart_sunzilog_port *up, unsigned char ch, int is_break, struct pt_regs *regs){ if (ZS_IS_KEYB(up)) { /* Stop-A is handled by drivers/char/keyboard.c now. */#ifdef CONFIG_SERIO if (up->serio_open) serio_interrupt(up->serio, ch, 0, regs);#endif } else if (ZS_IS_MOUSE(up)) { int ret = suncore_mouse_baud_detection(ch, is_break); switch (ret) { case 2: sunzilog_change_mouse_baud(up); /* fallthru */ case 1: break; case 0:#ifdef CONFIG_SERIO if (up->serio_open) serio_interrupt(up->serio, ch, 0, regs);#endif break; }; }}static struct tty_struct *sunzilog_receive_chars(struct uart_sunzilog_port *up, struct zilog_channel *channel, struct pt_regs *regs){ struct tty_struct *tty; unsigned char ch, r1; tty = NULL; if (up->port.info != NULL && /* Unopened serial console */ up->port.info->tty != NULL) /* Keyboard || mouse */ tty = up->port.info->tty; for (;;) { r1 = read_zsreg(channel, R1); if (r1 & (PAR_ERR | Rx_OVR | CRC_ERR)) { sbus_writeb(ERR_RES, &channel->control); ZSDELAY(); ZS_WSYNC(channel); } ch = sbus_readb(&channel->control); ZSDELAY(); /* This funny hack depends upon BRK_ABRT not interfering * with the other bits we care about in R1. */ if (ch & BRK_ABRT) r1 |= BRK_ABRT; if (!(ch & Rx_CH_AV)) break; ch = sbus_readb(&channel->data); ZSDELAY(); ch &= up->parity_mask; if (unlikely(ZS_IS_KEYB(up)) || unlikely(ZS_IS_MOUSE(up))) { sunzilog_kbdms_receive_chars(up, ch, 0, regs); continue; } if (tty == NULL) { uart_handle_sysrq_char(&up->port, ch, regs); continue; } if (unlikely(tty->flip.count >= TTY_FLIPBUF_SIZE)) { tty->flip.work.func((void *)tty); /* * The 8250 bails out of the loop here, * but we need to read everything, or die. */ if (tty->flip.count >= TTY_FLIPBUF_SIZE) continue; } /* A real serial line, record the character and status. */ *tty->flip.char_buf_ptr = ch; *tty->flip.flag_buf_ptr = TTY_NORMAL; up->port.icount.rx++; if (r1 & (BRK_ABRT | PAR_ERR | Rx_OVR | CRC_ERR)) { if (r1 & BRK_ABRT) { r1 &= ~(PAR_ERR | CRC_ERR); up->port.icount.brk++; if (uart_handle_break(&up->port)) continue; } else if (r1 & PAR_ERR) up->port.icount.parity++; else if (r1 & CRC_ERR) up->port.icount.frame++; if (r1 & Rx_OVR) up->port.icount.overrun++; r1 &= up->port.read_status_mask; if (r1 & BRK_ABRT) *tty->flip.flag_buf_ptr = TTY_BREAK; else if (r1 & PAR_ERR) *tty->flip.flag_buf_ptr = TTY_PARITY; else if (r1 & CRC_ERR) *tty->flip.flag_buf_ptr = TTY_FRAME; } if (uart_handle_sysrq_char(&up->port, ch, regs)) continue; if (up->port.ignore_status_mask == 0xff || (r1 & up->port.ignore_status_mask) == 0) { tty->flip.flag_buf_ptr++; tty->flip.char_buf_ptr++; tty->flip.count++; } if ((r1 & Rx_OVR) && tty->flip.count < TTY_FLIPBUF_SIZE) { *tty->flip.flag_buf_ptr = TTY_OVERRUN; tty->flip.flag_buf_ptr++; tty->flip.char_buf_ptr++; tty->flip.count++; } } return tty;}static void sunzilog_status_handle(struct uart_sunzilog_port *up, struct zilog_channel *channel, struct pt_regs *regs){ unsigned char status; status = sbus_readb(&channel->control); ZSDELAY(); sbus_writeb(RES_EXT_INT, &channel->control); ZSDELAY(); ZS_WSYNC(channel); if (status & BRK_ABRT) { if (ZS_IS_MOUSE(up)) sunzilog_kbdms_receive_chars(up, 0, 1, regs); if (ZS_IS_CONS(up)) { /* Wait for BREAK to deassert to avoid potentially * confusing the PROM. */ while (1) { status = sbus_readb(&channel->control); ZSDELAY(); if (!(status & BRK_ABRT)) break; } sun_do_break(); return; } } if (ZS_WANTS_MODEM_STATUS(up)) { if (status & SYNC) up->port.icount.dsr++; /* The Zilog just gives us an interrupt when DCD/CTS/etc. change. * But it does not tell us which bit has changed, we have to keep * track of this ourselves. */ if ((status ^ up->prev_status) ^ DCD) uart_handle_dcd_change(&up->port, (status & DCD)); if ((status ^ up->prev_status) ^ CTS) uart_handle_cts_change(&up->port, (status & CTS)); wake_up_interruptible(&up->port.info->delta_msr_wait); } up->prev_status = status;}static void sunzilog_transmit_chars(struct uart_sunzilog_port *up, struct zilog_channel *channel){ struct circ_buf *xmit; if (ZS_IS_CONS(up)) { unsigned char status = sbus_readb(&channel->control); ZSDELAY(); /* TX still busy? Just wait for the next TX done interrupt. * * It can occur because of how we do serial console writes. It would * be nice to transmit console writes just like we normally would for * a TTY line. (ie. buffered and TX interrupt driven). That is not * easy because console writes cannot sleep. One solution might be * to poll on enough port->xmit space becomming free. -DaveM */ if (!(status & Tx_BUF_EMP)) return; } up->flags &= ~SUNZILOG_FLAG_TX_ACTIVE; if (ZS_REGS_HELD(up)) { __load_zsregs(channel, up->curregs); up->flags &= ~SUNZILOG_FLAG_REGS_HELD; } if (ZS_TX_STOPPED(up)) { up->flags &= ~SUNZILOG_FLAG_TX_STOPPED; goto ack_tx_int; } if (up->port.x_char) { up->flags |= SUNZILOG_FLAG_TX_ACTIVE; sbus_writeb(up->port.x_char, &channel->data); ZSDELAY(); ZS_WSYNC(channel); up->port.icount.tx++; up->port.x_char = 0; return; } if (up->port.info == NULL) goto ack_tx_int; xmit = &up->port.info->xmit; if (uart_circ_empty(xmit)) { uart_write_wakeup(&up->port); goto ack_tx_int; } if (uart_tx_stopped(&up->port)) goto ack_tx_int; up->flags |= SUNZILOG_FLAG_TX_ACTIVE; sbus_writeb(xmit->buf[xmit->tail], &channel->data); ZSDELAY(); ZS_WSYNC(channel); xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); up->port.icount.tx++; if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(&up->port); return;ack_tx_int: sbus_writeb(RES_Tx_P, &channel->control); ZSDELAY(); ZS_WSYNC(channel);}static irqreturn_t sunzilog_interrupt(int irq, void *dev_id, struct pt_regs *regs){ struct uart_sunzilog_port *up = dev_id; while (up) { struct zilog_channel *channel = ZILOG_CHANNEL_FROM_PORT(&up->port); struct tty_struct *tty; unsigned char r3; spin_lock(&up->port.lock); r3 = read_zsreg(channel, R3); /* Channel A */ tty = NULL; if (r3 & (CHAEXT | CHATxIP | CHARxIP)) { sbus_writeb(RES_H_IUS, &channel->control); ZSDELAY(); ZS_WSYNC(channel); if (r3 & CHARxIP) tty = sunzilog_receive_chars(up, channel, regs); if (r3 & CHAEXT) sunzilog_status_handle(up, channel, regs); if (r3 & CHATxIP) sunzilog_transmit_chars(up, channel); } spin_unlock(&up->port.lock); if (tty) tty_flip_buffer_push(tty); /* Channel B */ up = up->next; channel = ZILOG_CHANNEL_FROM_PORT(&up->port); spin_lock(&up->port.lock); tty = NULL; if (r3 & (CHBEXT | CHBTxIP | CHBRxIP)) { sbus_writeb(RES_H_IUS, &channel->control); ZSDELAY(); ZS_WSYNC(channel); if (r3 & CHBRxIP) tty = sunzilog_receive_chars(up, channel, regs); if (r3 & CHBEXT)
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?