pnx8xxx_uart.c

来自「linux 内核源代码」· C语言 代码 · 共 853 行 · 第 1/2 页

C
853
字号
/* * UART driver for PNX8XXX SoCs * * Author: Per Hallsmark per.hallsmark@mvista.com * Ported to 2.6 kernel by EmbeddedAlley * Reworked by Vitaly Wool <vitalywool@gmail.com> * * Based on drivers/char/serial.c, by Linus Torvalds, Theodore Ts'o. * Copyright (C) 2000 Deep Blue Solutions Ltd. * * This file is licensed under the terms of the GNU General Public License * version 2. This program is licensed "as is" without any warranty of * any kind, whether express or implied. * */#if defined(CONFIG_SERIAL_PNX8XXX_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)#define SUPPORT_SYSRQ#endif#include <linux/module.h>#include <linux/ioport.h>#include <linux/init.h>#include <linux/console.h>#include <linux/sysrq.h>#include <linux/device.h>#include <linux/platform_device.h>#include <linux/tty.h>#include <linux/tty_flip.h>#include <linux/serial_core.h>#include <linux/serial.h>#include <linux/serial_pnx8xxx.h>#include <asm/io.h>#include <asm/irq.h>/* We'll be using StrongARM sa1100 serial port major/minor */#define SERIAL_PNX8XXX_MAJOR	204#define MINOR_START		5#define NR_PORTS		2#define PNX8XXX_ISR_PASS_LIMIT	256/* * Convert from ignore_status_mask or read_status_mask to FIFO * and interrupt status bits */#define SM_TO_FIFO(x)	((x) >> 10)#define SM_TO_ISTAT(x)	((x) & 0x000001ff)#define FIFO_TO_SM(x)	((x) << 10)#define ISTAT_TO_SM(x)	((x) & 0x000001ff)/* * This is the size of our serial port register set. */#define UART_PORT_SIZE	0x1000/* * This determines how often we check the modem status signals * for any change.  They generally aren't connected to an IRQ * so we have to poll them.  We also check immediately before * filling the TX fifo incase CTS has been dropped. */#define MCTRL_TIMEOUT	(250*HZ/1000)extern struct pnx8xxx_port pnx8xxx_ports[];static inline int serial_in(struct pnx8xxx_port *sport, int offset){	return (__raw_readl(sport->port.membase + offset));}static inline void serial_out(struct pnx8xxx_port *sport, int offset, int value){	__raw_writel(value, sport->port.membase + offset);}/* * Handle any change of modem status signal since we were last called. */static void pnx8xxx_mctrl_check(struct pnx8xxx_port *sport){	unsigned int status, changed;	status = sport->port.ops->get_mctrl(&sport->port);	changed = status ^ sport->old_status;	if (changed == 0)		return;	sport->old_status = status;	if (changed & TIOCM_RI)		sport->port.icount.rng++;	if (changed & TIOCM_DSR)		sport->port.icount.dsr++;	if (changed & TIOCM_CAR)		uart_handle_dcd_change(&sport->port, status & TIOCM_CAR);	if (changed & TIOCM_CTS)		uart_handle_cts_change(&sport->port, status & TIOCM_CTS);	wake_up_interruptible(&sport->port.info->delta_msr_wait);}/* * This is our per-port timeout handler, for checking the * modem status signals. */static void pnx8xxx_timeout(unsigned long data){	struct pnx8xxx_port *sport = (struct pnx8xxx_port *)data;	unsigned long flags;	if (sport->port.info) {		spin_lock_irqsave(&sport->port.lock, flags);		pnx8xxx_mctrl_check(sport);		spin_unlock_irqrestore(&sport->port.lock, flags);		mod_timer(&sport->timer, jiffies + MCTRL_TIMEOUT);	}}/* * interrupts disabled on entry */static void pnx8xxx_stop_tx(struct uart_port *port){	struct pnx8xxx_port *sport = (struct pnx8xxx_port *)port;	u32 ien;	/* Disable TX intr */	ien = serial_in(sport, PNX8XXX_IEN);	serial_out(sport, PNX8XXX_IEN, ien & ~PNX8XXX_UART_INT_ALLTX);	/* Clear all pending TX intr */	serial_out(sport, PNX8XXX_ICLR, PNX8XXX_UART_INT_ALLTX);}/* * interrupts may not be disabled on entry */static void pnx8xxx_start_tx(struct uart_port *port){	struct pnx8xxx_port *sport = (struct pnx8xxx_port *)port;	u32 ien;	/* Clear all pending TX intr */	serial_out(sport, PNX8XXX_ICLR, PNX8XXX_UART_INT_ALLTX);	/* Enable TX intr */	ien = serial_in(sport, PNX8XXX_IEN);	serial_out(sport, PNX8XXX_IEN, ien | PNX8XXX_UART_INT_ALLTX);}/* * Interrupts enabled */static void pnx8xxx_stop_rx(struct uart_port *port){	struct pnx8xxx_port *sport = (struct pnx8xxx_port *)port;	u32 ien;	/* Disable RX intr */	ien = serial_in(sport, PNX8XXX_IEN);	serial_out(sport, PNX8XXX_IEN, ien & ~PNX8XXX_UART_INT_ALLRX);	/* Clear all pending RX intr */	serial_out(sport, PNX8XXX_ICLR, PNX8XXX_UART_INT_ALLRX);}/* * Set the modem control timer to fire immediately. */static void pnx8xxx_enable_ms(struct uart_port *port){	struct pnx8xxx_port *sport = (struct pnx8xxx_port *)port;	mod_timer(&sport->timer, jiffies);}static void pnx8xxx_rx_chars(struct pnx8xxx_port *sport){	struct tty_struct *tty = sport->port.info->tty;	unsigned int status, ch, flg;	status = FIFO_TO_SM(serial_in(sport, PNX8XXX_FIFO)) |		 ISTAT_TO_SM(serial_in(sport, PNX8XXX_ISTAT));	while (status & FIFO_TO_SM(PNX8XXX_UART_FIFO_RXFIFO)) {		ch = serial_in(sport, PNX8XXX_FIFO);		sport->port.icount.rx++;		flg = TTY_NORMAL;		/*		 * note that the error handling code is		 * out of the main execution path		 */		if (status & (FIFO_TO_SM(PNX8XXX_UART_FIFO_RXFE |					PNX8XXX_UART_FIFO_RXPAR) |			      ISTAT_TO_SM(PNX8XXX_UART_INT_RXOVRN))) {			if (status & FIFO_TO_SM(PNX8XXX_UART_FIFO_RXPAR))				sport->port.icount.parity++;			else if (status & FIFO_TO_SM(PNX8XXX_UART_FIFO_RXFE))				sport->port.icount.frame++;			if (status & ISTAT_TO_SM(PNX8XXX_UART_INT_RXOVRN))				sport->port.icount.overrun++;			status &= sport->port.read_status_mask;			if (status & FIFO_TO_SM(PNX8XXX_UART_FIFO_RXPAR))				flg = TTY_PARITY;			else if (status & FIFO_TO_SM(PNX8XXX_UART_FIFO_RXFE))				flg = TTY_FRAME;#ifdef SUPPORT_SYSRQ			sport->port.sysrq = 0;#endif		}		if (uart_handle_sysrq_char(&sport->port, ch))			goto ignore_char;		uart_insert_char(&sport->port, status,				ISTAT_TO_SM(PNX8XXX_UART_INT_RXOVRN), ch, flg);	ignore_char:		serial_out(sport, PNX8XXX_LCR, serial_in(sport, PNX8XXX_LCR) |				PNX8XXX_UART_LCR_RX_NEXT);		status = FIFO_TO_SM(serial_in(sport, PNX8XXX_FIFO)) |			 ISTAT_TO_SM(serial_in(sport, PNX8XXX_ISTAT));	}	tty_flip_buffer_push(tty);}static void pnx8xxx_tx_chars(struct pnx8xxx_port *sport){	struct circ_buf *xmit = &sport->port.info->xmit;	if (sport->port.x_char) {		serial_out(sport, PNX8XXX_FIFO, sport->port.x_char);		sport->port.icount.tx++;		sport->port.x_char = 0;		return;	}	/*	 * Check the modem control lines before	 * transmitting anything.	 */	pnx8xxx_mctrl_check(sport);	if (uart_circ_empty(xmit) || uart_tx_stopped(&sport->port)) {		pnx8xxx_stop_tx(&sport->port);		return;	}	/*	 * TX while bytes available	 */	while (((serial_in(sport, PNX8XXX_FIFO) &					PNX8XXX_UART_FIFO_TXFIFO) >> 16) < 16) {		serial_out(sport, PNX8XXX_FIFO, xmit->buf[xmit->tail]);		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);		sport->port.icount.tx++;		if (uart_circ_empty(xmit))			break;	}	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)		uart_write_wakeup(&sport->port);	if (uart_circ_empty(xmit))		pnx8xxx_stop_tx(&sport->port);}static irqreturn_t pnx8xxx_int(int irq, void *dev_id){	struct pnx8xxx_port *sport = dev_id;	unsigned int status;	spin_lock(&sport->port.lock);	/* Get the interrupts */	status  = serial_in(sport, PNX8XXX_ISTAT) & serial_in(sport, PNX8XXX_IEN);	/* Break signal received */	if (status & PNX8XXX_UART_INT_BREAK) {		sport->port.icount.brk++;		uart_handle_break(&sport->port);	}	/* Byte received */	if (status & PNX8XXX_UART_INT_RX)		pnx8xxx_rx_chars(sport);	/* TX holding register empty - transmit a byte */	if (status & PNX8XXX_UART_INT_TX)		pnx8xxx_tx_chars(sport);	/* Clear the ISTAT register */	serial_out(sport, PNX8XXX_ICLR, status);	spin_unlock(&sport->port.lock);	return IRQ_HANDLED;}/* * Return TIOCSER_TEMT when transmitter is not busy. */static unsigned int pnx8xxx_tx_empty(struct uart_port *port){	struct pnx8xxx_port *sport = (struct pnx8xxx_port *)port;	return serial_in(sport, PNX8XXX_FIFO) & PNX8XXX_UART_FIFO_TXFIFO_STA ? 0 : TIOCSER_TEMT;}static unsigned int pnx8xxx_get_mctrl(struct uart_port *port){	struct pnx8xxx_port *sport = (struct pnx8xxx_port *)port;	unsigned int mctrl = TIOCM_DSR;	unsigned int msr;	/* REVISIT */	msr = serial_in(sport, PNX8XXX_MCR);	mctrl |= msr & PNX8XXX_UART_MCR_CTS ? TIOCM_CTS : 0;	mctrl |= msr & PNX8XXX_UART_MCR_DCD ? TIOCM_CAR : 0;	return mctrl;}static void pnx8xxx_set_mctrl(struct uart_port *port, unsigned int mctrl){#if	0	/* FIXME */	struct pnx8xxx_port *sport = (struct pnx8xxx_port *)port;	unsigned int msr;#endif}/* * Interrupts always disabled. */static void pnx8xxx_break_ctl(struct uart_port *port, int break_state){	struct pnx8xxx_port *sport = (struct pnx8xxx_port *)port;	unsigned long flags;	unsigned int lcr;	spin_lock_irqsave(&sport->port.lock, flags);	lcr = serial_in(sport, PNX8XXX_LCR);	if (break_state == -1)		lcr |= PNX8XXX_UART_LCR_TXBREAK;	else		lcr &= ~PNX8XXX_UART_LCR_TXBREAK;	serial_out(sport, PNX8XXX_LCR, lcr);	spin_unlock_irqrestore(&sport->port.lock, flags);}static int pnx8xxx_startup(struct uart_port *port){	struct pnx8xxx_port *sport = (struct pnx8xxx_port *)port;	int retval;	/*	 * Allocate the IRQ	 */	retval = request_irq(sport->port.irq, pnx8xxx_int, 0,			     "pnx8xxx-uart", sport);	if (retval)		return retval;	/*	 * Finally, clear and enable interrupts	 */	serial_out(sport, PNX8XXX_ICLR, PNX8XXX_UART_INT_ALLRX |			     PNX8XXX_UART_INT_ALLTX);	serial_out(sport, PNX8XXX_IEN, serial_in(sport, PNX8XXX_IEN) |			    PNX8XXX_UART_INT_ALLRX |			    PNX8XXX_UART_INT_ALLTX);	/*	 * Enable modem status interrupts	 */	spin_lock_irq(&sport->port.lock);	pnx8xxx_enable_ms(&sport->port);	spin_unlock_irq(&sport->port.lock);	return 0;}static void pnx8xxx_shutdown(struct uart_port *port){	struct pnx8xxx_port *sport = (struct pnx8xxx_port *)port;	int lcr;	/*	 * Stop our timer.	 */	del_timer_sync(&sport->timer);	/*	 * Disable all interrupts	 */	serial_out(sport, PNX8XXX_IEN, 0);	/*	 * Reset the Tx and Rx FIFOS, disable the break condition	 */	lcr = serial_in(sport, PNX8XXX_LCR);	lcr &= ~PNX8XXX_UART_LCR_TXBREAK;	lcr |= PNX8XXX_UART_LCR_TX_RST | PNX8XXX_UART_LCR_RX_RST;	serial_out(sport, PNX8XXX_LCR, lcr);	/*	 * Clear all interrupts	 */	serial_out(sport, PNX8XXX_ICLR, PNX8XXX_UART_INT_ALLRX |			     PNX8XXX_UART_INT_ALLTX);	/*	 * Free the interrupt	 */	free_irq(sport->port.irq, sport);

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?