📄 pmac_zilog.c
字号:
/* * linux/drivers/serial/pmac_zilog.c * * Driver for PowerMac Z85c30 based ESCC cell found in the * "macio" ASICs of various PowerMac models * * Copyright (C) 2003 Ben. Herrenschmidt (benh@kernel.crashing.org) * * Derived from drivers/macintosh/macserial.c by Paul Mackerras * and drivers/serial/sunzilog.c by David S. Miller * * Hrm... actually, I ripped most of sunzilog (Thanks David !) and * adapted special tweaks needed for us. I don't think it's worth * merging back those though. The DMA code still has to get in * and once done, I expect that driver to remain fairly stable in * the long term, unless we change the driver model again... * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * TODO: - Add DMA support * - Defer port shutdown to a few seconds after close * - maybe put something right into uap->clk_divisor */#undef DEBUG#undef DEBUG_HARD#include <linux/config.h>#include <linux/module.h>#include <linux/tty.h>#include <linux/tty_flip.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 <linux/init.h>#include <linux/console.h>#include <linux/slab.h>#include <linux/adb.h>#include <linux/pmu.h>#include <asm/sections.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/prom.h>#include <asm/bitops.h>#include <asm/machdep.h>#include <asm/pmac_feature.h>#include <asm/dbdma.h>#include <asm/macio.h>#include <asm/semaphore.h>#include <linux/serial.h>#include <linux/serial_core.h>#include "pmac_zilog.h"/* Not yet implemented */#undef HAS_DBDMAstatic char version[] __initdata = "pmac_zilog: 0.6 (Benjamin Herrenschmidt <benh@kernel.crashing.org>)";MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");MODULE_DESCRIPTION("Driver for the PowerMac serial ports.");MODULE_LICENSE("GPL");#define PWRDBG(fmt, arg...) printk(KERN_DEBUG fmt , ## arg)/* * For the sake of early serial console, we can do a pre-probe * (optional) of the ports at rather early boot time. */static struct uart_pmac_port pmz_ports[MAX_ZS_PORTS];static int pmz_ports_count;static DECLARE_MUTEX(pmz_irq_sem);static struct uart_driver pmz_uart_reg = { .owner = THIS_MODULE, .driver_name = "ttyS", .devfs_name = "tts/", .dev_name = "ttyS", .major = TTY_MAJOR,};/* * Load all registers to reprogram the port * 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 pmz_load_zsregs(struct uart_pmac_port *uap, u8 *regs){ int i; if (ZS_IS_ASLEEP(uap)) return; /* Let pending transmits finish. */ for (i = 0; i < 1000; i++) { unsigned char stat = read_zsreg(uap, R1); if (stat & ALL_SNT) break; udelay(100); } ZS_CLEARERR(uap); zssync(uap); ZS_CLEARFIFO(uap); zssync(uap); ZS_CLEARERR(uap); /* Disable all interrupts. */ write_zsreg(uap, R1, regs[R1] & ~(RxINT_MASK | TxINT_ENAB | EXT_INT_ENAB)); /* Set parity, sync config, stop bits, and clock divisor. */ write_zsreg(uap, R4, regs[R4]); /* Set misc. TX/RX control bits. */ write_zsreg(uap, R10, regs[R10]); /* Set TX/RX controls sans the enable bits. */ write_zsreg(uap, R3, regs[R3] & ~RxENABLE); write_zsreg(uap, R5, regs[R5] & ~TxENABLE); /* now set R7 "prime" on ESCC */ write_zsreg(uap, R15, regs[R15] | EN85C30); write_zsreg(uap, R7, regs[R7P]); /* make sure we use R7 "non-prime" on ESCC */ write_zsreg(uap, R15, regs[R15] & ~EN85C30); /* Synchronous mode config. */ write_zsreg(uap, R6, regs[R6]); write_zsreg(uap, R7, regs[R7]); /* Disable baud generator. */ write_zsreg(uap, R14, regs[R14] & ~BRENAB); /* Clock mode control. */ write_zsreg(uap, R11, regs[R11]); /* Lower and upper byte of baud rate generator divisor. */ write_zsreg(uap, R12, regs[R12]); write_zsreg(uap, R13, regs[R13]); /* Now rewrite R14, with BRENAB (if set). */ write_zsreg(uap, R14, regs[R14]); /* Reset external status interrupts. */ write_zsreg(uap, R0, RES_EXT_INT); write_zsreg(uap, R0, RES_EXT_INT); /* Rewrite R3/R5, this time without enables masked. */ write_zsreg(uap, R3, regs[R3]); write_zsreg(uap, R5, regs[R5]); /* Rewrite R1, this time without IRQ enabled masked. */ write_zsreg(uap, R1, regs[R1]); /* Enable interrupts */ write_zsreg(uap, R9, regs[R9]);}/* * We do like sunzilog to avoid disrupting pending Tx * 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 pmz_maybe_update_regs(struct uart_pmac_port *uap){ if (!ZS_REGS_HELD(uap)) { if (ZS_TX_ACTIVE(uap)) { uap->flags |= PMACZILOG_FLAG_REGS_HELD; } else { pmz_debug("pmz: maybe_update_regs: updating\n"); pmz_load_zsregs(uap, uap->curregs); } }}static struct tty_struct *pmz_receive_chars(struct uart_pmac_port *uap, struct pt_regs *regs){ struct tty_struct *tty = NULL; unsigned char ch, r1, drop, error; int loops = 0; retry: /* The interrupt can be enabled when the port isn't open, typically * that happens when using one port is open and the other closed (stale * interrupt) or when one port is used as a console. */ if (!ZS_IS_OPEN(uap)) { pmz_debug("pmz: draining input\n"); /* Port is closed, drain input data */ for (;;) { if ((++loops) > 1000) goto flood; (void)read_zsreg(uap, R1); write_zsreg(uap, R0, ERR_RES); (void)read_zsdata(uap); ch = read_zsreg(uap, R0); if (!(ch & Rx_CH_AV)) break; } return NULL; } /* Sanity check, make sure the old bug is no longer happening */ if (uap->port.info == NULL || uap->port.info->tty == NULL) { WARN_ON(1); (void)read_zsdata(uap); return NULL; } tty = uap->port.info->tty; while (1) { error = 0; drop = 0; if (unlikely(tty->flip.count >= TTY_FLIPBUF_SIZE)) { /* Have to drop the lock here */ pmz_debug("pmz: flip overflow\n"); spin_unlock(&uap->port.lock); tty->flip.work.func((void *)tty); spin_lock(&uap->port.lock); if (tty->flip.count >= TTY_FLIPBUF_SIZE) drop = 1; if (ZS_IS_ASLEEP(uap)) return NULL; if (!ZS_IS_OPEN(uap)) goto retry; } r1 = read_zsreg(uap, R1); ch = read_zsdata(uap); if (r1 & (PAR_ERR | Rx_OVR | CRC_ERR)) { write_zsreg(uap, R0, ERR_RES); zssync(uap); } ch &= uap->parity_mask; if (ch == 0 && uap->prev_status & BRK_ABRT) r1 |= BRK_ABRT; /* A real serial line, record the character and status. */ if (drop) goto next_char; *tty->flip.char_buf_ptr = ch; *tty->flip.flag_buf_ptr = TTY_NORMAL; uap->port.icount.rx++; if (r1 & (PAR_ERR | Rx_OVR | CRC_ERR | BRK_ABRT)) { error = 1; if (r1 & BRK_ABRT) { pmz_debug("pmz: got break !\n"); r1 &= ~(PAR_ERR | CRC_ERR); uap->port.icount.brk++; if (uart_handle_break(&uap->port)) { pmz_debug("pmz: do handle break !\n"); goto next_char; } } else if (r1 & PAR_ERR) uap->port.icount.parity++; else if (r1 & CRC_ERR) uap->port.icount.frame++; if (r1 & Rx_OVR) uap->port.icount.overrun++; r1 &= uap->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(&uap->port, ch, regs)) { pmz_debug("pmz: sysrq swallowed the char\n"); goto next_char; } if (uap->port.ignore_status_mask == 0xff || (r1 & uap->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: /* We can get stuck in an infinite loop getting char 0 when the * line is in a wrong HW state, we break that here. * When that happens, I disable the receive side of the driver. * Note that what I've been experiencing is a real irq loop where * I'm getting flooded regardless of the actual port speed. * Something stange is going on with the HW */ if ((++loops) > 1000) goto flood; ch = read_zsreg(uap, R0); if (!(ch & Rx_CH_AV)) break; } return tty; flood: uap->curregs[R1] &= ~(EXT_INT_ENAB | TxINT_ENAB | RxINT_MASK); write_zsreg(uap, R1, uap->curregs[R1]); zssync(uap); dev_err(&uap->dev->ofdev.dev, "pmz: rx irq flood !\n"); return tty;}static void pmz_status_handle(struct uart_pmac_port *uap, struct pt_regs *regs){ unsigned char status; status = read_zsreg(uap, R0); write_zsreg(uap, R0, RES_EXT_INT); zssync(uap); if (ZS_IS_OPEN(uap) && ZS_WANTS_MODEM_STATUS(uap)) { if (status & SYNC_HUNT) uap->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. * The CTS input is inverted for some reason. -- paulus */ if ((status ^ uap->prev_status) & DCD) uart_handle_dcd_change(&uap->port, (status & DCD)); if ((status ^ uap->prev_status) & CTS) uart_handle_cts_change(&uap->port, !(status & CTS)); wake_up_interruptible(&uap->port.info->delta_msr_wait); } uap->prev_status = status;}static void pmz_transmit_chars(struct uart_pmac_port *uap){ struct circ_buf *xmit; if (ZS_IS_ASLEEP(uap)) return; if (ZS_IS_CONS(uap)) { unsigned char status = read_zsreg(uap, R0); /* 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; } uap->flags &= ~PMACZILOG_FLAG_TX_ACTIVE; if (ZS_REGS_HELD(uap)) { pmz_load_zsregs(uap, uap->curregs); uap->flags &= ~PMACZILOG_FLAG_REGS_HELD; } if (ZS_TX_STOPPED(uap)) { uap->flags &= ~PMACZILOG_FLAG_TX_STOPPED; goto ack_tx_int; } if (uap->port.x_char) { uap->flags |= PMACZILOG_FLAG_TX_ACTIVE; write_zsdata(uap, uap->port.x_char); zssync(uap); uap->port.icount.tx++; uap->port.x_char = 0; return; } if (uap->port.info == NULL) goto ack_tx_int; xmit = &uap->port.info->xmit; if (uart_circ_empty(xmit)) { uart_write_wakeup(&uap->port); goto ack_tx_int; } if (uart_tx_stopped(&uap->port)) goto ack_tx_int; uap->flags |= PMACZILOG_FLAG_TX_ACTIVE; write_zsdata(uap, xmit->buf[xmit->tail]); zssync(uap); xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); uap->port.icount.tx++; if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(&uap->port); return;ack_tx_int: write_zsreg(uap, R0, RES_Tx_P); zssync(uap);}/* Hrm... we register that twice, fixme later.... */static irqreturn_t pmz_interrupt(int irq, void *dev_id, struct pt_regs *regs){ struct uart_pmac_port *uap = dev_id; struct uart_pmac_port *uap_a; struct uart_pmac_port *uap_b; int rc = IRQ_NONE; struct tty_struct *tty; u8 r3; uap_a = pmz_get_port_A(uap); uap_b = uap_a->mate; spin_lock(&uap_a->port.lock); r3 = read_zsreg(uap_a, R3);#ifdef DEBUG_HARD pmz_debug("irq, r3: %x\n", r3);#endif /* Channel A */ tty = NULL; if (r3 & (CHAEXT | CHATxIP | CHARxIP)) { write_zsreg(uap_a, R0, RES_H_IUS); zssync(uap_a); if (r3 & CHAEXT) pmz_status_handle(uap_a, regs); if (r3 & CHARxIP) tty = pmz_receive_chars(uap_a, regs); if (r3 & CHATxIP) pmz_transmit_chars(uap_a); rc = IRQ_HANDLED; } spin_unlock(&uap_a->port.lock); if (tty != NULL) tty_flip_buffer_push(tty); if (uap_b->node == NULL) goto out; spin_lock(&uap_b->port.lock); tty = NULL; if (r3 & (CHBEXT | CHBTxIP | CHBRxIP)) { write_zsreg(uap_b, R0, RES_H_IUS); zssync(uap_b); if (r3 & CHBEXT) pmz_status_handle(uap_b, regs); if (r3 & CHBRxIP) tty = pmz_receive_chars(uap_b, regs); if (r3 & CHBTxIP) pmz_transmit_chars(uap_b); rc = IRQ_HANDLED; } spin_unlock(&uap_b->port.lock); if (tty != NULL) tty_flip_buffer_push(tty); out:#ifdef DEBUG_HARD pmz_debug("irq done.\n");#endif return rc;}/* * Peek the status register, lock not held by caller */static inline u8 pmz_peek_status(struct uart_pmac_port *uap){
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -