mcfserial.c

来自「Linux Kernel 2.6.9 for OMAP1710」· C语言 代码 · 共 1,892 行 · 第 1/4 页

C
1,892
字号
/* * mcfserial.c -- serial driver for ColdFire internal UARTS. * * Copyright (C) 1999-2003 Greg Ungerer <gerg@snapgear.com> * Copyright (c) 2000-2001 Lineo, Inc. <www.lineo.com>  * Copyright (C) 2001-2002 SnapGear Inc. <www.snapgear.com>  * * Based on code from 68332serial.c which was: * * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) * Copyright (C) 1998 TSHG * Copyright (c) 1999 Rt-Control Inc. <jeff@uclinux.org> * * Changes: * 08/07/2003    Daniele Bellucci <bellucda@tiscali.it> *               some cleanups in mcfrs_write. * */ #include <linux/module.h>#include <linux/errno.h>#include <linux/signal.h>#include <linux/sched.h>#include <linux/timer.h>#include <linux/wait.h>#include <linux/interrupt.h>#include <linux/tty.h>#include <linux/tty_flip.h>#include <linux/string.h>#include <linux/fcntl.h>#include <linux/mm.h>#include <linux/kernel.h>#include <linux/serial.h>#include <linux/serialP.h>#include <linux/console.h>#include <linux/init.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/system.h>#include <asm/segment.h>#include <asm/semaphore.h>#include <asm/bitops.h>#include <asm/delay.h>#include <asm/coldfire.h>#include <asm/mcfsim.h>#include <asm/mcfuart.h>#include <asm/nettel.h>#include <asm/uaccess.h>#include "mcfserial.h"struct timer_list mcfrs_timer_struct;/* *	Default console baud rate,  we use this as the default *	for all ports so init can just open /dev/console and *	keep going.  Perhaps one day the cflag settings for the *	console can be used instead. */#if defined(CONFIG_ARNEWSH) || defined(CONFIG_MOTOROLA) || defined(CONFIG_senTec)#define	CONSOLE_BAUD_RATE	19200#define	DEFAULT_CBAUD		B19200#endif#ifndef CONSOLE_BAUD_RATE#define	CONSOLE_BAUD_RATE	9600#define	DEFAULT_CBAUD		B9600#endifint mcfrs_console_inited = 0;int mcfrs_console_port = -1;int mcfrs_console_baud = CONSOLE_BAUD_RATE;int mcfrs_console_cbaud = DEFAULT_CBAUD;/* *	Driver data structures. */static struct tty_driver *mcfrs_serial_driver;/* number of characters left in xmit buffer before we ask for more */#define WAKEUP_CHARS 256/* Debugging... */#undef SERIAL_DEBUG_OPEN#undef SERIAL_DEBUG_FLOW#ifdef CONFIG_M5282#define	IRQBASE	77#else#define	IRQBASE	73#endif/* *	Configuration table, UARTs to look for at startup. */static struct mcf_serial mcfrs_table[] = {	{  /* ttyS0 */		.magic = 0,		.addr = (volatile unsigned char *) (MCF_MBAR+MCFUART_BASE1),		.irq = IRQBASE,		.flags = ASYNC_BOOT_AUTOCONF,	},	{  /* ttyS1 */		.magic = 0,		.addr = (volatile unsigned char *) (MCF_MBAR+MCFUART_BASE2),		.irq = IRQBASE+1,		.flags = ASYNC_BOOT_AUTOCONF,	},};#define	NR_PORTS	(sizeof(mcfrs_table) / sizeof(struct mcf_serial))/* * This is used to figure out the divisor speeds and the timeouts. */static int mcfrs_baud_table[] = {	0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,	9600, 19200, 38400, 57600, 115200, 230400, 460800, 0};#define MCFRS_BAUD_TABLE_SIZE \			(sizeof(mcfrs_baud_table)/sizeof(mcfrs_baud_table[0]))#ifdef CONFIG_MAGIC_SYSRQ/* *	Magic system request keys. Used for debugging... */extern int	magic_sysrq_key(int ch);#endif/* * tmp_buf is used as a temporary buffer by serial_write.  We need to * lock it in case the copy_from_user blocks while swapping in a page, * and some other program tries to do a serial write at the same time. * Since the lock will only come under contention when the system is * swapping and available memory is low, it makes sense to share one * buffer across all the serial ports, since it significantly saves * memory if large numbers of serial ports are open. */static unsigned char mcfrs_tmp_buf[4096]; /* This is cheating */static DECLARE_MUTEX(mcfrs_tmp_buf_sem);/* *	Forware declarations... */static void	mcfrs_change_speed(struct mcf_serial *info);static void	mcfrs_wait_until_sent(struct tty_struct *tty, int timeout);static inline int serial_paranoia_check(struct mcf_serial *info,					char *name, const char *routine){#ifdef SERIAL_PARANOIA_CHECK	static const char badmagic[] =		"MCFRS(warning): bad magic number for serial struct %s in %s\n";	static const char badinfo[] =		"MCFRS(warning): null mcf_serial for %s in %s\n";	if (!info) {		printk(badinfo, name, routine);		return 1;	}	if (info->magic != SERIAL_MAGIC) {		printk(badmagic, name, routine);		return 1;	}#endif	return 0;}/* *	Sets or clears DTR and RTS on the requested line. */static void mcfrs_setsignals(struct mcf_serial *info, int dtr, int rts){	volatile unsigned char	*uartp;	unsigned long		flags;	#if 0	printk("%s(%d): mcfrs_setsignals(info=%x,dtr=%d,rts=%d)\n",		__FILE__, __LINE__, info, dtr, rts);#endif	local_irq_save(flags);	if (dtr >= 0) {#ifdef MCFPP_DTR0		if (info->line)			mcf_setppdata(MCFPP_DTR1, (dtr ? 0 : MCFPP_DTR1));		else			mcf_setppdata(MCFPP_DTR0, (dtr ? 0 : MCFPP_DTR0));#endif	}	if (rts >= 0) {		uartp = info->addr;		if (rts) {			info->sigs |= TIOCM_RTS;			uartp[MCFUART_UOP1] = MCFUART_UOP_RTS;		} else {			info->sigs &= ~TIOCM_RTS;			uartp[MCFUART_UOP0] = MCFUART_UOP_RTS;		}	}	local_irq_restore(flags);	return;}/* *	Gets values of serial signals. */static int mcfrs_getsignals(struct mcf_serial *info){	volatile unsigned char	*uartp;	unsigned long		flags;	int			sigs;#if defined(CONFIG_NETtel) && defined(CONFIG_M5307)	unsigned short		ppdata;#endif#if 0	printk("%s(%d): mcfrs_getsignals(info=%x)\n", __FILE__, __LINE__);#endif	local_irq_save(flags);	uartp = info->addr;	sigs = (uartp[MCFUART_UIPR] & MCFUART_UIPR_CTS) ? 0 : TIOCM_CTS;	sigs |= (info->sigs & TIOCM_RTS);#ifdef MCFPP_DCD0{	unsigned int ppdata;	ppdata = mcf_getppdata();	if (info->line == 0) {		sigs |= (ppdata & MCFPP_DCD0) ? 0 : TIOCM_CD;		sigs |= (ppdata & MCFPP_DTR0) ? 0 : TIOCM_DTR;	} else if (info->line == 1) {		sigs |= (ppdata & MCFPP_DCD1) ? 0 : TIOCM_CD;		sigs |= (ppdata & MCFPP_DTR1) ? 0 : TIOCM_DTR;	}}#endif	local_irq_restore(flags);	return(sigs);}/* * ------------------------------------------------------------ * mcfrs_stop() and mcfrs_start() * * This routines are called before setting or resetting tty->stopped. * They enable or disable transmitter interrupts, as necessary. * ------------------------------------------------------------ */static void mcfrs_stop(struct tty_struct *tty){	volatile unsigned char	*uartp;	struct mcf_serial	*info = (struct mcf_serial *)tty->driver_data;	unsigned long		flags;	if (serial_paranoia_check(info, tty->name, "mcfrs_stop"))		return;		local_irq_save(flags);	uartp = info->addr;	info->imr &= ~MCFUART_UIR_TXREADY;	uartp[MCFUART_UIMR] = info->imr;	local_irq_restore(flags);}static void mcfrs_start(struct tty_struct *tty){	volatile unsigned char	*uartp;	struct mcf_serial	*info = (struct mcf_serial *)tty->driver_data;	unsigned long		flags;		if (serial_paranoia_check(info, tty->name, "mcfrs_start"))		return;	local_irq_save(flags);	if (info->xmit_cnt && info->xmit_buf) {		uartp = info->addr;		info->imr |= MCFUART_UIR_TXREADY;		uartp[MCFUART_UIMR] = info->imr;	}	local_irq_restore(flags);}/* * ---------------------------------------------------------------------- * * Here starts the interrupt handling routines.  All of the following * subroutines are declared as inline and are folded into * mcfrs_interrupt().  They were separated out for readability's sake. * * Note: mcfrs_interrupt() is a "fast" interrupt, which means that it * runs with interrupts turned off.  People who may want to modify * mcfrs_interrupt() should try to keep the interrupt handler as fast as * possible.  After you are done making modifications, it is not a bad * idea to do: *  * gcc -S -DKERNEL -Wall -Wstrict-prototypes -O6 -fomit-frame-pointer serial.c * * and look at the resulting assemble code in serial.s. * * 				- Ted Ts'o (tytso@mit.edu), 7-Mar-93 * ----------------------------------------------------------------------- */static inline void receive_chars(struct mcf_serial *info){	volatile unsigned char	*uartp;	struct tty_struct	*tty = info->tty;	unsigned char		status, ch;	if (!tty)		return;	uartp = info->addr;	while ((status = uartp[MCFUART_USR]) & MCFUART_USR_RXREADY) {		if (tty->flip.count >= TTY_FLIPBUF_SIZE)			break;		ch = uartp[MCFUART_URB];		info->stats.rx++;#ifdef CONFIG_MAGIC_SYSRQ		if (mcfrs_console_inited && (info->line == mcfrs_console_port)) {			if (magic_sysrq_key(ch))				continue;		}#endif		tty->flip.count++;		if (status & MCFUART_USR_RXERR)			uartp[MCFUART_UCR] = MCFUART_UCR_CMDRESETERR;		if (status & MCFUART_USR_RXBREAK) {			info->stats.rxbreak++;			*tty->flip.flag_buf_ptr++ = TTY_BREAK;		} else if (status & MCFUART_USR_RXPARITY) {			info->stats.rxparity++;			*tty->flip.flag_buf_ptr++ = TTY_PARITY;		} else if (status & MCFUART_USR_RXOVERRUN) {			info->stats.rxoverrun++;			*tty->flip.flag_buf_ptr++ = TTY_OVERRUN;		} else if (status & MCFUART_USR_RXFRAMING) {			info->stats.rxframing++;			*tty->flip.flag_buf_ptr++ = TTY_FRAME;		} else {			*tty->flip.flag_buf_ptr++ = 0;		}		*tty->flip.char_buf_ptr++ = ch;	}	schedule_work(&tty->flip.work);	return;}static inline void transmit_chars(struct mcf_serial *info){	volatile unsigned char	*uartp;	uartp = info->addr;	if (info->x_char) {		/* Send special char - probably flow control */		uartp[MCFUART_UTB] = info->x_char;		info->x_char = 0;		info->stats.tx++;	}	if ((info->xmit_cnt <= 0) || info->tty->stopped) {		info->imr &= ~MCFUART_UIR_TXREADY;		uartp[MCFUART_UIMR] = info->imr;		return;	}	while (uartp[MCFUART_USR] & MCFUART_USR_TXREADY) {		uartp[MCFUART_UTB] = info->xmit_buf[info->xmit_tail++];		info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1);		info->stats.tx++;		if (--info->xmit_cnt <= 0)			break;	}	if (info->xmit_cnt < WAKEUP_CHARS)		schedule_work(&info->tqueue);	return;}/* * This is the serial driver's generic interrupt routine */irqreturn_t mcfrs_interrupt(int irq, void *dev_id, struct pt_regs *regs){	struct mcf_serial	*info;	unsigned char		isr;	info = &mcfrs_table[(irq - IRQBASE)];	isr = info->addr[MCFUART_UISR] & info->imr;	if (isr & MCFUART_UIR_RXREADY)		receive_chars(info);	if (isr & MCFUART_UIR_TXREADY)		transmit_chars(info);	return IRQ_HANDLED;}/* * ------------------------------------------------------------------- * Here ends the serial interrupt routines. * ------------------------------------------------------------------- */static void mcfrs_offintr(void *private){	struct mcf_serial	*info = (struct mcf_serial *) private;	struct tty_struct	*tty;		tty = info->tty;	if (!tty)		return;	tty_wakeup(tty);}/* *	Change of state on a DCD line. */void mcfrs_modem_change(struct mcf_serial *info, int dcd){	if (info->count == 0)		return;	if (info->flags & ASYNC_CHECK_CD) {		if (dcd)			wake_up_interruptible(&info->open_wait);		else 			schedule_work(&info->tqueue_hangup);	}}#ifdef MCFPP_DCD0unsigned short	mcfrs_ppstatus;/* * This subroutine is called when the RS_TIMER goes off. It is used * to monitor the state of the DCD lines - since they have no edge * sensors and interrupt generators. */static void mcfrs_timer(void){	unsigned int	ppstatus, dcdval, i;	ppstatus = mcf_getppdata() & (MCFPP_DCD0 | MCFPP_DCD1);	if (ppstatus != mcfrs_ppstatus) {		for (i = 0; (i < 2); i++) {			dcdval = (i ? MCFPP_DCD1 : MCFPP_DCD0);			if ((ppstatus & dcdval) != (mcfrs_ppstatus & dcdval)) {				mcfrs_modem_change(&mcfrs_table[i],					((ppstatus & dcdval) ? 0 : 1));			}		}	}	mcfrs_ppstatus = ppstatus;

⌨️ 快捷键说明

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