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 + -
显示快捷键?