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 + -
显示快捷键?