📄 ip22zilog.c
字号:
/* * Driver for Zilog serial chips found on SGI workstations and * servers. This driver could actually be made more generic. * * This is based on the drivers/serial/sunzilog.c code as of 2.6.0-test7 and the * old drivers/sgi/char/sgiserial.c code which itself is based of the original * 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 David S. Miller, Eddie C. Dost, Pete Zaitcev, Ted Ts'o and Alex Buell * for their work there. * * Copyright (C) 2002 Ralf Baechle (ralf@linux-mips.org) * Copyright (C) 2002 David S. Miller (davem@redhat.com) */#include <linux/module.h>#include <linux/kernel.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>#include <linux/init.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/sgialib.h>#include <asm/sgi/ioc.h>#include <asm/sgi/hpc3.h>#include <asm/sgi/ip22.h>#if defined(CONFIG_SERIAL_IP22_ZILOG_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)#define SUPPORT_SYSRQ#endif#include <linux/serial_core.h>#include "ip22zilog.h"/* * On IP22 we need to delay after register accesses but we do not need to * flush writes. */#define ZSDELAY() udelay(5)#define ZSDELAY_LONG() udelay(20)#define ZS_WSYNC(channel) do { } while (0)#define NUM_IP22ZILOG 1#define NUM_CHANNELS (NUM_IP22ZILOG * 2)#define ZS_CLOCK 3672000 /* 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_ip22zilog_port { struct uart_port port; /* IRQ servicing chain. */ struct uart_ip22zilog_port *next; /* Current values of Zilog write registers. */ unsigned char curregs[NUM_ZSREGS]; unsigned int flags;#define IP22ZILOG_FLAG_IS_CONS 0x00000004#define IP22ZILOG_FLAG_IS_KGDB 0x00000008#define IP22ZILOG_FLAG_MODEM_STATUS 0x00000010#define IP22ZILOG_FLAG_IS_CHANNEL_A 0x00000020#define IP22ZILOG_FLAG_REGS_HELD 0x00000040#define IP22ZILOG_FLAG_TX_STOPPED 0x00000080#define IP22ZILOG_FLAG_TX_ACTIVE 0x00000100#define IP22ZILOG_FLAG_RESET_DONE 0x00000200 unsigned int tty_break; unsigned char parity_mask; unsigned char prev_status;};#define ZILOG_CHANNEL_FROM_PORT(PORT) ((struct zilog_channel *)((PORT)->membase))#define UART_ZILOG(PORT) ((struct uart_ip22zilog_port *)(PORT))#define IP22ZILOG_GET_CURR_REG(PORT, REGNUM) \ (UART_ZILOG(PORT)->curregs[REGNUM])#define IP22ZILOG_SET_CURR_REG(PORT, REGNUM, REGVAL) \ ((UART_ZILOG(PORT)->curregs[REGNUM]) = (REGVAL))#define ZS_IS_CONS(UP) ((UP)->flags & IP22ZILOG_FLAG_IS_CONS)#define ZS_IS_KGDB(UP) ((UP)->flags & IP22ZILOG_FLAG_IS_KGDB)#define ZS_WANTS_MODEM_STATUS(UP) ((UP)->flags & IP22ZILOG_FLAG_MODEM_STATUS)#define ZS_IS_CHANNEL_A(UP) ((UP)->flags & IP22ZILOG_FLAG_IS_CHANNEL_A)#define ZS_REGS_HELD(UP) ((UP)->flags & IP22ZILOG_FLAG_REGS_HELD)#define ZS_TX_STOPPED(UP) ((UP)->flags & IP22ZILOG_FLAG_TX_STOPPED)#define ZS_TX_ACTIVE(UP) ((UP)->flags & IP22ZILOG_FLAG_TX_ACTIVE)/* Reading and writing Zilog8530 registers. The delays are to make this * driver work on the IP22 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; writeb(reg, &channel->control); ZSDELAY(); retval = readb(&channel->control); ZSDELAY(); return retval;}static void write_zsreg(struct zilog_channel *channel, unsigned char reg, unsigned char value){ writeb(reg, &channel->control); ZSDELAY(); writeb(value, &channel->control); ZSDELAY();}static void ip22zilog_clear_fifo(struct zilog_channel *channel){ int i; for (i = 0; i < 32; i++) { unsigned char regval; regval = readb(&channel->control); ZSDELAY(); if (regval & Rx_CH_AV) break; regval = read_zsreg(channel, R1); readb(&channel->data); ZSDELAY(); if (regval & (PAR_ERR | Rx_OVR | CRC_ERR)) { 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); } writeb(ERR_RES, &channel->control); ZSDELAY(); ZS_WSYNC(channel); ip22zilog_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 ip22zilog_maybe_update_regs(struct uart_ip22zilog_port *up, struct zilog_channel *channel){ if (!ZS_REGS_HELD(up)) { if (ZS_TX_ACTIVE(up)) { up->flags |= IP22ZILOG_FLAG_REGS_HELD; } else { __load_zsregs(channel, up->curregs); } }}#define Rx_BRK 0x0100 /* BREAK event software flag. */#define Rx_SYS 0x0200 /* SysRq event software flag. */static struct tty_struct *ip22zilog_receive_chars(struct uart_ip22zilog_port *up, struct zilog_channel *channel){ struct tty_struct *tty; unsigned char ch, flag; unsigned int r1; tty = NULL; if (up->port.info != NULL && up->port.info->tty != NULL) tty = up->port.info->tty; for (;;) { ch = readb(&channel->control); ZSDELAY(); if (!(ch & Rx_CH_AV)) break; r1 = read_zsreg(channel, R1); if (r1 & (PAR_ERR | Rx_OVR | CRC_ERR)) { writeb(ERR_RES, &channel->control); ZSDELAY(); ZS_WSYNC(channel); } ch = readb(&channel->data); ZSDELAY(); ch &= up->parity_mask; /* Handle the null char got when BREAK is removed. */ if (!ch) r1 |= up->tty_break; /* A real serial line, record the character and status. */ flag = TTY_NORMAL; up->port.icount.rx++; if (r1 & (PAR_ERR | Rx_OVR | CRC_ERR | Rx_SYS | Rx_BRK)) { up->tty_break = 0; if (r1 & (Rx_SYS | Rx_BRK)) { up->port.icount.brk++; if (r1 & Rx_SYS) continue; r1 &= ~(PAR_ERR | CRC_ERR); } 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 & Rx_BRK) flag = TTY_BREAK; else if (r1 & PAR_ERR) flag = TTY_PARITY; else if (r1 & CRC_ERR) flag = TTY_FRAME; } if (uart_handle_sysrq_char(&up->port, ch)) continue; if (tty) uart_insert_char(&up->port, r1, Rx_OVR, ch, flag); } return tty;}static void ip22zilog_status_handle(struct uart_ip22zilog_port *up, struct zilog_channel *channel){ unsigned char status; status = readb(&channel->control); ZSDELAY(); writeb(RES_EXT_INT, &channel->control); ZSDELAY(); ZS_WSYNC(channel); if (up->curregs[R15] & BRKIE) { if ((status & BRK_ABRT) && !(up->prev_status & BRK_ABRT)) { if (uart_handle_break(&up->port)) up->tty_break = Rx_SYS; else up->tty_break = Rx_BRK; } } 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 ip22zilog_transmit_chars(struct uart_ip22zilog_port *up, struct zilog_channel *channel){ struct circ_buf *xmit; if (ZS_IS_CONS(up)) { unsigned char status = 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 &= ~IP22ZILOG_FLAG_TX_ACTIVE; if (ZS_REGS_HELD(up)) { __load_zsregs(channel, up->curregs); up->flags &= ~IP22ZILOG_FLAG_REGS_HELD; } if (ZS_TX_STOPPED(up)) { up->flags &= ~IP22ZILOG_FLAG_TX_STOPPED; goto ack_tx_int; } if (up->port.x_char) { up->flags |= IP22ZILOG_FLAG_TX_ACTIVE; 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;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -