📄 serial_atmel.c
字号:
/* serial port driver for the Atmel AT91 series builtin USARTs * * Copyright (C) 2000, 2001 Erik Andersen <andersen@lineo.com> * * Based on: * drivers/char/68302serial.c * and also based on trioserial.c from Aplio, though this driver * has been extensively changed since then. No author was * listed in trioserial.c. *//* Enable this to force this driver to always operate at 57600 */#undef FORCE_57600#include <linux/config.h>#include <linux/version.h>#include <linux/types.h>#include <linux/serial.h>#include <linux/errno.h>#include <linux/signal.h>#include <linux/sched.h>#include <linux/timer.h>#include <linux/interrupt.h>#include <linux/tty.h>#include <linux/tty_flip.h>#include <linux/config.h>#include <linux/major.h>#include <linux/string.h>#include <linux/fcntl.h>#include <linux/mm.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/console.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/arch/irq.h>#include <asm/system.h>#include <asm/segment.h>#include <asm/bitops.h>#include <asm/delay.h>#include <asm/uaccess.h>#include "serial_atmel.h"#define USE_INTS 1#define UART_CLOCK (ARM_CLK/16)#define SERIAL_XMIT_SIZE PAGE_SIZE#define RX_SERIAL_SIZE 256/* serial subtype definitions */#define SERIAL_TYPE_NORMAL 1#define SERIAL_TYPE_CALLOUT 2/* number of characters left in xmit buffer before we ask for more */#define WAKEUP_CHARS 256/* Debugging... DEBUG_INTR is bad to use when one of the zs * lines is your console ;( */#undef SERIAL_DEBUG_INTR#undef SERIAL_DEBUG_OPEN#undef SERIAL_DEBUG_FLOW#define RS_ISR_PASS_LIMIT 256#define _INLINE_#ifndef MIN#define MIN(a,b) ((a) < (b) ? (a) : (b))#endifstatic volatile struct atmel_usart_regs *usarts[AT91_USART_CNT] = { (volatile struct atmel_usart_regs *) AT91_USART0_BASE, (volatile struct atmel_usart_regs *) AT91_USART1_BASE};static struct atmel_serial atmel_info[AT91_USART_CNT];/* * tmp_buf is used as a temporary buffer by serial_write. We need to * lock it in case the memcpy_fromfs 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 tmp_buf[SERIAL_XMIT_SIZE]; /* This is cheating */DECLARE_MUTEX(tmp_buf_sem);static unsigned char * rx_buf_table[AT91_USART_CNT];static unsigned char rx_buf1[RX_SERIAL_SIZE];static unsigned char rx_buf2[RX_SERIAL_SIZE];/* Console hooks... */struct atmel_serial *atmel_consinfo = 0;DECLARE_TASK_QUEUE(tq_atmel_serial);static struct tq_struct serialpoll;static struct tty_driver serial_driver, callout_driver;static int serial_refcount;static void serpoll(void *data);static void change_speed(struct atmel_serial *info);static struct tty_struct *serial_table[AT91_USART_CNT];static struct termios *serial_termios[AT91_USART_CNT];static struct termios *serial_termios_locked[AT91_USART_CNT];static char prompt0;static void xmit_char(struct atmel_serial *info, char ch);static void xmit_string(struct atmel_serial *info, char *p, int len);static void start_rx(struct atmel_serial *info);static void wait_EOT(volatile struct atmel_usart_regs *);static void uart_init(struct atmel_serial *info);static void uart_speed(struct atmel_serial *info, unsigned cflag);static void tx_enable(volatile struct atmel_usart_regs *uart);static void rx_enable(volatile struct atmel_usart_regs *uart);static void tx_disable(volatile struct atmel_usart_regs *uart);static void rx_disable(volatile struct atmel_usart_regs *uart);static void tx_stop(volatile struct atmel_usart_regs *uart);static void tx_start(volatile struct atmel_usart_regs *uart, int ints);static void rx_stop(volatile struct atmel_usart_regs *uart);static void rx_start(volatile struct atmel_usart_regs *uart, int ints);static void set_ints_mode(int yes, struct atmel_serial *info);static void rs_interrupt(struct atmel_serial *info);extern void show_net_buffers(void);extern void hard_reset_now(void);static void handle_termios_tcsets(struct termios * ptermios, struct atmel_serial * ptty);static int global;static void coucou1(void){ global = 0;}static void coucou2(void){ global = 1;}static void _INLINE_ tx_enable(volatile struct atmel_usart_regs *uart){ uart->ier = US_TXEMPTY;}static void _INLINE_ rx_enable(volatile struct atmel_usart_regs *uart){ uart->ier = US_ENDRX | US_TIMEOUT;}static void _INLINE_ tx_disable(volatile struct atmel_usart_regs *uart){ uart->idr = US_TXEMPTY;}static void _INLINE_ rx_disable(volatile struct atmel_usart_regs *uart){ uart->idr = US_ENDRX | US_TIMEOUT;}static void _INLINE_ tx_stop(volatile struct atmel_usart_regs *uart){ tx_disable(uart); uart->tcr = 0; uart->cr = US_TXDIS;}static void _INLINE_ tx_start(volatile struct atmel_usart_regs *uart, int ints){ if (ints) { tx_enable(uart); } uart->cr = US_TXEN;}static void _INLINE_ rx_stop(volatile struct atmel_usart_regs *uart){ rx_disable(uart); uart->rtor = 0; uart->rcr = 0; uart->cr = US_RXDIS;}static void _INLINE_ rx_start(volatile struct atmel_usart_regs *uart, int ints){ uart->cr = US_RXEN | US_STTO; uart->rtor = 20; if (ints) { rx_enable(uart); }}static void _INLINE_ reset_status(volatile struct atmel_usart_regs *uart){ uart->cr = US_RSTSTA;}static void set_ints_mode(int yes, struct atmel_serial *info){ info->use_ints = yes;// FIXME: check#if 0 (yes) ? unmask_irq(info->irq) : mask_irq(info->irq);#endif}#ifdef US_RTSstatic void atmel_cts_off(struct atmel_serial *info){ volatile struct atmel_usart_regs *uart; uart = info->usart; uart->mc &= ~(unsigned long) US_RTS; info->cts_state = 0;}static void atmel_cts_on(struct atmel_serial *info){ volatile struct atmel_usart_regs *uart; uart = info->usart; uart->mc |= US_RTS; info->cts_state = 1;}/* Sets or clears DTR/RTS on the requested line */static inline void atmel_rtsdtr(struct atmel_serial *ss, int set){ volatile struct atmel_usart_regs *uart; uart = ss->usart; if (set) { uart->mc |= US_DTR | US_RTS; } else { uart->mc &= ~(unsigned long) (US_DTR | US_RTS); } return;}#endif /* US_RTS */static inline int serial_paranoia_check(struct atmel_serial *info, dev_t device, const char *routine){#ifdef SERIAL_PARANOIA_CHECK static const char *badmagic = "Warning: bad magic number for serial struct (%d, %d) in %s\n"; static const char *badinfo = "Warning: null atmel_serial struct for (%d, %d) in %s\n"; if (!info) { printk(badinfo, MAJOR(device), MINOR(device), routine); return 1; } if (info->magic != SERIAL_MAGIC) { printk(badmagic, MAJOR(device), MINOR(device), routine); return 1; }#endif return 0;}/* * ------------------------------------------------------------ * rs_stop() and rs_start() * * This routines are called before setting or resetting tty->stopped. * They enable or disable transmitter interrupts, as necessary. * ------------------------------------------------------------ */static void rs_stop(struct tty_struct *tty){ struct atmel_serial *info = (struct atmel_serial *) tty->driver_data; unsigned long flags; if (serial_paranoia_check(info, tty->device, "rs_stop")) return; save_flags(flags); cli(); tx_stop(info->usart); rx_stop(info->usart); restore_flags(flags);}static void rs_put_char(struct atmel_serial *info, char ch){ int flags = 0; save_flags(flags); cli(); xmit_char(info, ch); wait_EOT(info->usart); restore_flags(flags);}static void rs_start(struct tty_struct *tty){ struct atmel_serial *info = (struct atmel_serial *) tty->driver_data; unsigned long flags; if (serial_paranoia_check(info, tty->device, "rs_start")) return; save_flags(flags); cli(); tx_start(info->usart, info->use_ints); rx_start(info->usart, info->use_ints); /* FIXME */// start_rx(info); restore_flags(flags);}/* Drop into either the boot monitor or kadb upon receiving a break * from keyboard/console input. */static void batten_down_hatches(void){#if 0 /* If we are doing kadb, we call the debugger * else we just drop into the boot monitor. * Note that we must flush the user windows * first before giving up control. */ if ((((unsigned long) linux_dbvec) >= DEBUG_FIRSTVADDR) && (((unsigned long) linux_dbvec) <= DEBUG_LASTVADDR)) sp_enter_debugger(); else panic("atmel_serial: batten_down_hatches"); return;#endif}/* * ---------------------------------------------------------------------- * * Here starts the interrupt handling routines. All of the following * subroutines are declared as inline and are folded into * rs_interrupt(). They were separated out for readability's sake. * * Note: rs_interrupt() is a "fast" interrupt, which means that it * runs with interrupts turned off. People who may want to modify * rs_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 assembly code in serial.s. * * - Ted Ts'o (tytso@mit.edu), 7-Mar-93 * ----------------------------------------------------------------------- *//* * This routine is used by the interrupt handler to schedule * processing in the software interrupt portion of the driver. */static _INLINE_ void rs_sched_event(struct atmel_serial *info, int event){ info->event |= 1 << event; queue_task(&info->tqueue, &tq_atmel_serial); mark_bh(SERIAL_BH);}extern void breakpoint(void); /* For the KGDB frame character */static _INLINE_ void receive_chars(struct atmel_serial *info, unsigned long status){// unsigned char ch; FIXME int count; volatile struct atmel_usart_regs *uart = info->usart;#if 0 // hack to receive chars by polling from anywhere struct atmel_serial *info1 = &atmel_info; struct tty_struct *tty = info1->tty; if (!(info->flags & S_INITIALIZED)) return;#else struct tty_struct *tty = info->tty; if (!(info->flags & S_INITIALIZED)) return;#endif count = RX_SERIAL_SIZE - uart->rcr; // hack to receive chars by polling only BD fields if (!count) { return; }#if 0 ch = info->rx_buf[0]; if (info->is_cons) { if (status & US_RXBRK) { /* whee, break received */ batten_down_hatches(); /*rs_recv_clear(info->atmel_channel); */ return; } else if (ch == 0x10) { /* ^P */ show_state(); show_free_areas(); show_buffers(); show_net_buffers(); return; } else if (ch == 0x12) { /* ^R */ hard_reset_now(); return; } /* It is a 'keyboard interrupt' ;-) */ wake_up(&keypress_wait); }#endif#if 0 /* Look for kgdb 'stop' character, consult the gdb documentation * for remote target debugging and arch/sparc/kernel/sparc-stub.c * to see how all this works. */ if((info->kgdb_channel) && (ch =='\003')) { breakpoint(); goto clear_and_exit; }#endif if (!tty) goto clear_and_exit; if (tty->flip.count >= TTY_FLIPBUF_SIZE) queue_task(&tty->flip.tqueue, &tq_timer); if ((count + tty->flip.count) >= TTY_FLIPBUF_SIZE) {#ifdef US_RTS atmel_cts_off(info);#endif serialpoll.data = (void *) info; queue_task(&serialpoll, &tq_timer); } memset(tty->flip.flag_buf_ptr, 0, count); memcpy(tty->flip.char_buf_ptr, info->rx_buf, count); tty->flip.char_buf_ptr += count; if (status & US_PARE) *(tty->flip.flag_buf_ptr - 1) = TTY_PARITY; else if (status & US_OVRE) *(tty->flip.flag_buf_ptr - 1) = TTY_OVERRUN; else if (status & US_FRAME) *(tty->flip.flag_buf_ptr - 1) = TTY_FRAME; tty->flip.count += count; queue_task(&tty->flip.tqueue, &tq_timer); clear_and_exit: start_rx(info); return;}static _INLINE_ void transmit_chars(struct atmel_serial *info){ if (info->x_char) { /* Send next char */ xmit_char(info, info->x_char); info->x_char = 0; goto clear_and_return; } if ((info->xmit_cnt <= 0) || info->tty->stopped) { /* That's peculiar... */ tx_stop(info->usart); goto clear_and_return; } if (info->xmit_tail + info->xmit_cnt < SERIAL_XMIT_SIZE) { xmit_string(info, info->xmit_buf + info->xmit_tail, info->xmit_cnt); info->xmit_tail = (info->xmit_tail + info->xmit_cnt) & (SERIAL_XMIT_SIZE - 1); info->xmit_cnt = 0; } else { coucou1(); xmit_string(info, info->xmit_buf + info->xmit_tail, SERIAL_XMIT_SIZE - info->xmit_tail); //xmit_string(info, info->xmit_buf, info->xmit_tail + info->xmit_cnt - SERIAL_XMIT_SIZE); info->xmit_cnt = info->xmit_cnt - (SERIAL_XMIT_SIZE - info->xmit_tail); info->xmit_tail = 0; }#if 0 /* Send char */ xmit_char(info, info->xmit_buf[info->xmit_tail++]); info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE - 1); info->xmit_cnt--;#endif if (info->xmit_cnt < WAKEUP_CHARS) rs_sched_event(info, RS_EVENT_WRITE_WAKEUP); if (info->xmit_cnt <= 0) { //tx_stop(info->usart); goto clear_and_return; } clear_and_return: /* Clear interrupt (should be auto) */ return;}static _INLINE_ void status_handle(struct atmel_serial *info, unsigned long status){#if 0 if (status & DCD) { if ((info->tty->termios->c_cflag & CRTSCTS) && ((info->curregs[3] & AUTO_ENAB) == 0)) { info->curregs[3] |= AUTO_ENAB; info->pendregs[3] |= AUTO_ENAB; write_zsreg(info->atmel_channel, 3, info->curregs[3]); } } else { if ((info->curregs[3] & AUTO_ENAB)) { info->curregs[3] &= ~AUTO_ENAB; info->pendregs[3] &= ~AUTO_ENAB; write_zsreg(info->atmel_channel, 3, info->curregs[3]); } }#endif /* Whee, if this is console input and this is a * 'break asserted' status change interrupt, call * the boot prom. */ if ((status & US_RXBRK) && info->break_abort) batten_down_hatches(); /* XXX Whee, put in a buffer somewhere, the status information * XXX whee whee whee... Where does the information go... */ reset_status(info->usart); return;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -