📄 serial_amba.c
字号:
/* * linux/drivers/char/serial_amba.c * * Driver for AMBA serial ports * * Based on drivers/char/serial.c, by Linus Torvalds, Theodore Ts'o. * * Copyright 1999 ARM Limited * Copyright (C) 2000 Deep Blue Solutions Ltd. * * 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 * * * This is a generic driver for ARM AMBA-type serial ports. They * have a lot of 16550-like features, but are not register compatable. * Note that although they do have CTS, DCD and DSR inputs, they do * not have an RI input, nor do they have DTR or RTS outputs. If * required, these have to be supplied via some other means (eg, GPIO) * and hooked into this driver. * * This could very easily become a generic serial driver for dumb UARTs * (eg, {82,16x}50, 21285, SA1100). */#include <linux/config.h>#include <linux/module.h>#include <linux/errno.h>#include <linux/signal.h>#include <linux/sched.h>#include <linux/interrupt.h>#include <linux/tty.h>#include <linux/tty_flip.h>#include <linux/major.h>#include <linux/string.h>#include <linux/fcntl.h>#include <linux/ptrace.h>#include <linux/ioport.h>#include <linux/mm.h>#include <linux/malloc.h>#include <linux/init.h>#include <linux/circ_buf.h>#include <linux/serial.h>#include <linux/console.h>#include <linux/sysrq.h>#include <asm/system.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/uaccess.h>#include <asm/bitops.h>#include <asm/hardware/serial_amba.h>#define SERIAL_AMBA_NAME "ttyAM"#define SERIAL_AMBA_MAJOR 204#define SERIAL_AMBA_MINOR 16#define SERIAL_AMBA_NR 2#define CALLOUT_AMBA_NAME "cuaam"#define CALLOUT_AMBA_MAJOR 205#define CALLOUT_AMBA_MINOR 16#define CALLOUT_AMBA_NR SERIAL_AMBA_NR#ifndef TRUE#define TRUE 1#endif#ifndef FALSE#define FALSE 0#endif#define DEBUG 0#define DEBUG_LEDS 0#if DEBUG_LEDSextern int get_leds(void);extern int set_leds(int);#endif/* * Access routines for the AMBA UARTs */#define UART_GET_INT_STATUS(p) IO_READ((p)->uart_base + AMBA_UARTIIR)#define UART_GET_FR(p) IO_READ((p)->uart_base + AMBA_UARTFR)#define UART_GET_CHAR(p) IO_READ((p)->uart_base + AMBA_UARTDR)#define UART_PUT_CHAR(p, c) IO_WRITE((p)->uart_base + AMBA_UARTDR, (c))#define UART_GET_RSR(p) IO_READ((p)->uart_base + AMBA_UARTRSR)#define UART_GET_CR(p) IO_READ((p)->uart_base + AMBA_UARTCR)#define UART_PUT_CR(p,c) IO_WRITE((p)->uart_base + AMBA_UARTCR, (c))#define UART_GET_LCRL(p) IO_READ((p)->uart_base + AMBA_UARTLCR_L)#define UART_PUT_LCRL(p,c) IO_WRITE((p)->uart_base + AMBA_UARTLCR_L, (c))#define UART_GET_LCRM(p) IO_READ((p)->uart_base + AMBA_UARTLCR_M)#define UART_PUT_LCRM(p,c) IO_WRITE((p)->uart_base + AMBA_UARTLCR_M, (c))#define UART_GET_LCRH(p) IO_READ((p)->uart_base + AMBA_UARTLCR_H)#define UART_PUT_LCRH(p,c) IO_WRITE((p)->uart_base + AMBA_UARTLCR_H, (c))#define UART_RX_DATA(s) (((s) & AMBA_UARTFR_RXFE) == 0)#define UART_TX_READY(s) (((s) & AMBA_UARTFR_TXFF) == 0)#define UART_TX_EMPTY(p) ((UART_GET_FR(p) & AMBA_UARTFR_TMSK) == 0)#define AMBA_UARTRSR_ANY (AMBA_UARTRSR_OE|AMBA_UARTRSR_BE|AMBA_UARTRSR_PE|AMBA_UARTRSR_FE)#define AMBA_UARTFR_MODEM_ANY (AMBA_UARTFR_DCD|AMBA_UARTFR_DSR|AMBA_UARTFR_CTS)/* * Things needed by tty driver */static struct tty_driver ambanormal_driver, ambacallout_driver;static int ambauart_refcount;static struct tty_struct *ambauart_table[SERIAL_AMBA_NR];static struct termios *ambauart_termios[SERIAL_AMBA_NR];static struct termios *ambauart_termios_locked[SERIAL_AMBA_NR];#if defined(CONFIG_SERIAL_AMBA_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)#define SUPPORT_SYSRQ#endif/* * Things needed internally to this driver *//* * 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 u_char *tmp_buf;static DECLARE_MUTEX(tmp_buf_sem);#define HIGH_BITS_OFFSET ((sizeof(long)-sizeof(int))*8)/* number of characters left in xmit buffer before we ask for more */#define WAKEUP_CHARS 256#define AMBA_ISR_PASS_LIMIT 256#define EVT_WRITE_WAKEUP 0struct amba_icount { __u32 cts; __u32 dsr; __u32 rng; __u32 dcd; __u32 rx; __u32 tx; __u32 frame; __u32 overrun; __u32 parity; __u32 brk; __u32 buf_overrun;};/* * Static information about the port */struct amba_port { unsigned int uart_base; unsigned int irq; unsigned int uartclk; unsigned int fifosize; unsigned int tiocm_support; void (*set_mctrl)(struct amba_port *, u_int mctrl);}; /* * This is the state information which is persistent across opens */struct amba_state { struct amba_icount icount; unsigned int line; unsigned int close_delay; unsigned int closing_wait; unsigned int custom_divisor; unsigned int flags; struct termios normal_termios; struct termios callout_termios; int count; struct amba_info *info;};#define AMBA_XMIT_SIZE 1024/* * This is the state information which is only valid when the port is open. */struct amba_info { struct amba_port *port; struct amba_state *state; struct tty_struct *tty; unsigned char x_char; unsigned char old_status; unsigned char read_status_mask; unsigned char ignore_status_mask; struct circ_buf xmit; unsigned int flags;#ifdef SUPPORT_SYSRQ unsigned long sysrq;#endif unsigned int event; unsigned int timeout; unsigned int lcr_h; unsigned int mctrl; int blocked_open; pid_t session; pid_t pgrp; struct tasklet_struct tlet; wait_queue_head_t open_wait; wait_queue_head_t close_wait; wait_queue_head_t delta_msr_wait;};#ifdef CONFIG_SERIAL_AMBA_CONSOLEstatic struct console ambauart_cons;#endifstatic void ambauart_change_speed(struct amba_info *info, struct termios *old_termios);static void ambauart_wait_until_sent(struct tty_struct *tty, int timeout);#if 1 //def CONFIG_SERIAL_INTEGRATORstatic void amba_set_mctrl_null(struct amba_port *port, u_int mctrl){}static struct amba_port amba_ports[SERIAL_AMBA_NR] = { { uart_base: IO_ADDRESS(INTEGRATOR_UART0_BASE), irq: IRQ_UARTINT0, uartclk: 14745600, fifosize: 8, set_mctrl: amba_set_mctrl_null, }, { uart_base: IO_ADDRESS(INTEGRATOR_UART1_BASE), irq: IRQ_UARTINT1, uartclk: 14745600, fifosize: 8, set_mctrl: amba_set_mctrl_null, }};#endifstatic struct amba_state amba_state[SERIAL_AMBA_NR];static void ambauart_enable_rx_interrupt(struct amba_info *info){ unsigned int cr; cr = UART_GET_CR(info->port); cr |= AMBA_UARTCR_RIE | AMBA_UARTCR_RTIE; UART_PUT_CR(info->port, cr);}static void ambauart_disable_rx_interrupt(struct amba_info *info){ unsigned int cr; cr = UART_GET_CR(info->port); cr &= ~(AMBA_UARTCR_RIE | AMBA_UARTCR_RTIE); UART_PUT_CR(info->port, cr);}static void ambauart_enable_tx_interrupt(struct amba_info *info){ unsigned int cr; cr = UART_GET_CR(info->port); cr |= AMBA_UARTCR_TIE; UART_PUT_CR(info->port, cr);}static void ambauart_disable_tx_interrupt(struct amba_info *info){ unsigned int cr; cr = UART_GET_CR(info->port); cr &= ~AMBA_UARTCR_TIE; UART_PUT_CR(info->port, cr);}static void ambauart_stop(struct tty_struct *tty){ struct amba_info *info = tty->driver_data; unsigned long flags; save_flags(flags); cli(); ambauart_disable_tx_interrupt(info); restore_flags(flags);}static void ambauart_start(struct tty_struct *tty){ struct amba_info *info = tty->driver_data; unsigned long flags; save_flags(flags); cli(); if (info->xmit.head != info->xmit.tail && info->xmit.buf) ambauart_enable_tx_interrupt(info); restore_flags(flags);}/* * This routine is used by the interrupt handler to schedule * processing in the software interrupt portion of the driver. */static void ambauart_event(struct amba_info *info, int event){ info->event |= 1 << event; tasklet_schedule(&info->tlet);}static void#ifdef SUPPORT_SYSRQambauart_rx_chars(struct amba_info *info, struct pt_regs *regs)#elseambauart_rx_chars(struct amba_info *info)#endif{ struct tty_struct *tty = info->tty; unsigned int status, ch, rsr, flg, ignored = 0; struct amba_icount *icount = &info->state->icount; struct amba_port *port = info->port; status = UART_GET_FR(port); while (UART_RX_DATA(status)) { ch = UART_GET_CHAR(port); if (tty->flip.count >= TTY_FLIPBUF_SIZE) goto ignore_char; icount->rx++; flg = TTY_NORMAL; /* * Note that the error handling code is * out of the main execution path */ rsr = UART_GET_RSR(port); if (rsr & AMBA_UARTRSR_ANY) goto handle_error;#ifdef SUPPORT_SYSRQ if (info->sysrq) { if (ch && time_before(jiffies, info->sysrq)) { handle_sysrq(ch, regs, NULL, NULL); info->sysrq = 0; goto ignore_char; } info->sysrq = 0; }#endif error_return: *tty->flip.flag_buf_ptr++ = flg; *tty->flip.char_buf_ptr++ = ch; tty->flip.count++; ignore_char: status = UART_GET_FR(port); }out: tty_flip_buffer_push(tty); return;handle_error: if (rsr & AMBA_UARTRSR_BE) { rsr &= ~(AMBA_UARTRSR_FE | AMBA_UARTRSR_PE); icount->brk++;#ifdef SUPPORT_SYSRQ if (info->state->line == ambauart_cons.index) { if (!info->sysrq) { info->sysrq = jiffies + HZ*5; goto ignore_char; } }#endif } else if (rsr & AMBA_UARTRSR_PE) icount->parity++; else if (rsr & AMBA_UARTRSR_FE) icount->frame++; if (rsr & AMBA_UARTRSR_OE) icount->overrun++; if (rsr & info->ignore_status_mask) { if (++ignored > 100) goto out; goto ignore_char; } rsr &= info->read_status_mask; if (rsr & AMBA_UARTRSR_BE) flg = TTY_BREAK; else if (rsr & AMBA_UARTRSR_PE) flg = TTY_PARITY; else if (rsr & AMBA_UARTRSR_FE) flg = TTY_FRAME; if (rsr & AMBA_UARTRSR_OE) { /* * CHECK: does overrun affect the current character? * ASSUMPTION: it does not. */ *tty->flip.flag_buf_ptr++ = flg; *tty->flip.char_buf_ptr++ = ch; tty->flip.count++; if (tty->flip.count >= TTY_FLIPBUF_SIZE) goto ignore_char; ch = 0; flg = TTY_OVERRUN; }#ifdef SUPPORT_SYSRQ info->sysrq = 0;#endif goto error_return;}static void ambauart_tx_chars(struct amba_info *info){ struct amba_port *port = info->port; int count; if (info->x_char) { UART_PUT_CHAR(port, info->x_char); info->state->icount.tx++; info->x_char = 0; return; } if (info->xmit.head == info->xmit.tail || info->tty->stopped || info->tty->hw_stopped) { ambauart_disable_tx_interrupt(info); return; } count = port->fifosize; do { UART_PUT_CHAR(port, info->xmit.buf[info->xmit.tail]); info->xmit.tail = (info->xmit.tail + 1) & (AMBA_XMIT_SIZE - 1); info->state->icount.tx++; if (info->xmit.head == info->xmit.tail) break; } while (--count > 0); if (CIRC_CNT(info->xmit.head, info->xmit.tail, AMBA_XMIT_SIZE) < WAKEUP_CHARS) ambauart_event(info, EVT_WRITE_WAKEUP); if (info->xmit.head == info->xmit.tail) { ambauart_disable_tx_interrupt(info); }}static void ambauart_modem_status(struct amba_info *info){ unsigned int status, delta; struct amba_icount *icount = &info->state->icount; status = UART_GET_FR(info->port) & AMBA_UARTFR_MODEM_ANY; delta = status ^ info->old_status; info->old_status = status; if (!delta) return; if (delta & AMBA_UARTFR_DCD) { icount->dcd++;#ifdef CONFIG_HARD_PPS if ((info->flags & ASYNC_HARDPPS_CD) && (status & AMBA_UARTFR_DCD) hardpps();#endif if (info->flags & ASYNC_CHECK_CD) { if (status & AMBA_UARTFR_DCD) wake_up_interruptible(&info->open_wait); else if (!((info->flags & ASYNC_CALLOUT_ACTIVE) && (info->flags & ASYNC_CALLOUT_NOHUP))) { if (info->tty) tty_hangup(info->tty); } } } if (delta & AMBA_UARTFR_DSR) icount->dsr++; if (delta & AMBA_UARTFR_CTS) { icount->cts++; if (info->flags & ASYNC_CTS_FLOW) { status &= AMBA_UARTFR_CTS; if (info->tty->hw_stopped) { if (status) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -