ip22zilog.c
来自「Linux Kernel 2.6.9 for OMAP1710」· C语言 代码 · 共 1,308 行 · 第 1/3 页
C
1,308 行
/* * 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/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>#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"int ip22serial_current_minor = 64;void ip22_do_break(void);/* * 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 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_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 unsigned int cflag; /* L1-A keyboard break state. */ int kbd_id; int l1_down; 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); } }}static void ip22zilog_receive_chars(struct uart_ip22zilog_port *up, struct zilog_channel *channel, struct pt_regs *regs){ struct tty_struct *tty = up->port.info->tty; /* XXX info==NULL? */ while (1) { unsigned char ch, r1; if (unlikely(tty->flip.count >= TTY_FLIPBUF_SIZE)) { tty->flip.work.func((void *)tty); if (tty->flip.count >= TTY_FLIPBUF_SIZE) return; /* XXX Ignores SysRq when we need it most. Fix. */ } 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->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; ch = readb(&channel->data); ZSDELAY(); ch &= up->parity_mask; if (ZS_IS_CONS(up) && (r1 & BRK_ABRT)) { /* Wait for BREAK to deassert to avoid potentially * confusing the PROM. */ while (1) { ch = readb(&channel->control); ZSDELAY(); if (!(ch & BRK_ABRT)) break; } ip22_do_break(); return; } /* 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)) goto next_char; } 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)) goto next_char; 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++; } next_char: ch = readb(&channel->control); ZSDELAY(); if (!(ch & Rx_CH_AV)) break; } tty_flip_buffer_push(tty);}static void ip22zilog_status_handle(struct uart_ip22zilog_port *up, struct zilog_channel *channel, struct pt_regs *regs){ unsigned char status; status = readb(&channel->control); ZSDELAY(); writeb(RES_EXT_INT, &channel->control); ZSDELAY(); ZS_WSYNC(channel); 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 & DCD) ^ up->prev_status) uart_handle_dcd_change(&up->port, (status & DCD)); if ((status & CTS) ^ up->prev_status) 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)
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?