⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 pmac_zilog.c

📁 Linux Kernel 2.6.9 for OMAP1710
💻 C
📖 第 1 页 / 共 4 页
字号:
/* * 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 + -