📄 serial_s3c44b0x.c
字号:
/* * serial_s3c44b0x.c: Serial port driver for the Samsung S3C44B0X builtin UART * * Copyright (c) 2007 NSY Technologies * by Jesse Jiang <jiangxiaogen@sina.com> * * Copyright (c) 2004 sympat GmbH * by Michael Frommberger <michael.frommberger@sympat.de> * * Copyright (c) 2003 sympat GmbH * by Thomas Eschenbacher <thomas.eschenbacher@gmx.de> * * Copyright (c) 2001 Arcturus Networks Inc. * by Oleksandr Zhadan <oleks@arcturusnetworks.com> * Copyright (c) 2002 Arcturus Networks Inc. * by Michael Leslie <mleslie@arcturusnetworks.com> * * Based on: drivers/char/trioserial.c * Copyright (C) 1998 Kenneth Albanowski <kjahds@kjahds.com>, * D. Jeff Dionne <jeff@arcturusnetworks.com>, * The Silver Hammer Group, Ltd. * * Based on: drivers/char/68328serial.c * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * */#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/major.h>#include <linux/string.h>#include <linux/fcntl.h>#include <linux/mm.h>#include <linux/kernel.h>#include <linux/console.h>#include <linux/init.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/hardware.h>#define queue_task_irq_off queue_task#define copy_from_user(a,b,c) memcpy_fromfs(a,b,c)#define copy_to_user(a,b,c) memcpy_tofs(a,b,c)#include <asm/uaccess.h>#include "serial_s3c44b0x.h"#define USART_CNT 2 /* Receive FIFO trigger level */#define RX_FIFO_LEVEL 4 /* 0 (off) 4 or 8 or 12 or 16 */ /* Transfer FIFO trigger level */#define TX_FIFO_LEVEL 0 /* 0 or 4 or 8 or 12 */#define TX_FIFO_DEPTH (5) /* maximum transmit FIFO length */#define RX_FIFO_DEPTH (8) /* maximum receive FIFO length *//* 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#undef SERIAL_DEBUG_SPEED#undef SERIAL_DEBUG_THROTTLE#define _INLINE_ inline#ifndef MIN#define MIN(a,b) ((a) < (b) ? (a) : (b))#endifstatic struct s3c44b0x_serial s3c44b0x_info[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 */static DECLARE_MUTEX (tmp_buf_sem);/* Console hooks... */#if defined(CONFIG_SERIAL_S3C44B0X_CONSOLE)static int console_number = 0;#endif DECLARE_TASK_QUEUE(tq_s3c44b0x_serial);static struct tty_driver serial_driver, callout_driver;static int serial_refcount;static struct tty_struct *serial_table[USART_CNT];static struct termios *serial_termios[USART_CNT];static struct termios *serial_termios_locked[USART_CNT];static void change_speed (struct s3c44b0x_serial *info);static _INLINE_ void rx_enable (struct s3c44b0x_serial *info);static _INLINE_ void rx_disable (struct s3c44b0x_serial *info);static _INLINE_ void tx_enable (struct s3c44b0x_serial *info);static _INLINE_ void tx_disable (struct s3c44b0x_serial *info);static _INLINE_ void wait_tx_empty(struct s3c44b0x_serial *info, int with_scheduling);static _INLINE_ void tx_stop (struct s3c44b0x_serial *info);static _INLINE_ void tx_start (struct s3c44b0x_serial *info);static _INLINE_ void rx_stop (struct s3c44b0x_serial *info);static _INLINE_ void rx_start (struct s3c44b0x_serial *info);static _INLINE_ void xmit_char (struct s3c44b0x_serial *info, char ch);static _INLINE_ void rs_s3c44b0x_sched_event \ (struct s3c44b0x_serial *info, int event);static _INLINE_ void uart_speed (struct s3c44b0x_serial *info, unsigned int cflag);static _INLINE_ void handle_status (struct s3c44b0x_serial *info, unsigned int status);static _INLINE_ void fifo_reset (struct s3c44b0x_serial *info);static _INLINE_ void fifo_init (struct s3c44b0x_serial *info);static void set_ints_mode (struct s3c44b0x_serial *info, int yes);static void rs_s3c44b0x_interruptTxa (int irq, void *dev_id, struct pt_regs *regs);static void rs_s3c44b0x_interruptTxb (int irq, void *dev_id, struct pt_regs *regs);static void rs_s3c44b0x_interruptTx (int irq, void *dev_id, struct pt_regs *regs, struct s3c44b0x_serial *serial_info);static void rs_s3c44b0x_interruptRxa (int irq, void *dev_id, struct pt_regs *regs);static void rs_s3c44b0x_interruptRxb (int irq, void *dev_id, struct pt_regs *regs);static void rs_s3c44b0x_interruptRx (int irq, void *dev_id, struct pt_regs *regs, struct s3c44b0x_serial *serial_info);static void rs_s3c44b0x_interruptErr (int irq, void *dev_id, struct pt_regs *regs);extern void show_net_buffers (void);extern void hard_reset_now (void);static void handle_termios_tcsets(struct termios *ptermios, struct s3c44b0x_serial *pinfo);/******************************************************************************/static _INLINE_ void rx_enable(struct s3c44b0x_serial *info){ int ucon, ucon_old, ufcon; /* enable rx */ ucon = ucon_old = inl(S3C44B0X_UCON0 + info->uart_offset); ucon |= S3C44B0X_UCON_RX_MODE_INT_POLL; outl(ucon, S3C44B0X_UCON0 + info->uart_offset); /* flush fifo */ if (ucon_old != ucon){ ufcon = inl(S3C44B0X_UFCON0 + info->uart_offset); ufcon |= S3C44B0X_UFCON_RX_FIFO_RST; outl(ufcon, S3C44B0X_UFCON0 + info->uart_offset); }}static _INLINE_ void rx_disable(struct s3c44b0x_serial *info){ int ucon, ucon_rx; /* disable rx */ ucon = ucon_rx = inl(S3C44B0X_UCON0 + info->uart_offset); ucon_rx &= (S3C44B0X_UCON_RX_MASK & S3C44B0X_UCON_RX_DIS); ucon &= ~S3C44B0X_UCON_RX_MASK; ucon |= ucon_rx; outl(ucon, S3C44B0X_UCON0 + info->uart_offset);}static _INLINE_ void tx_enable(struct s3c44b0x_serial *info){ char umcon = inb(S3C44B0X_UMCON0 + info->uart_offset); umcon &= ~S3C44B0X_UMCON_AFC; /* disable auto flow-control */ umcon |= S3C44B0X_UMCON_RQST_SEND; outb(umcon, S3C44B0X_UMCON0 + info->uart_offset);}static _INLINE_ void tx_disable(struct s3c44b0x_serial *info){ char umcon; /* wait until data is sent */ wait_tx_empty(info, 0); /* disabel tx */ umcon = inb(S3C44B0X_UMCON0 + info->uart_offset); umcon &= ~S3C44B0X_UMCON_AFC; /* disable auto flow-control */ umcon &= ~S3C44B0X_UMCON_RQST_SEND; /* Inactivate nRTS */ outb(umcon, S3C44B0X_UMCON0 + info->uart_offset);}static _INLINE_ void wait_tx_empty(struct s3c44b0x_serial *info, int with_scheduling){ char ustat, ufstat; ustat = inb(S3C44B0X_UTRSTAT0 + info->uart_offset); ufstat = inb(S3C44B0X_UFSTAT0 + info->uart_offset); /* wait until data is sent */ do{ if (with_scheduling) yield(); ustat = inb(S3C44B0X_UTRSTAT0 + info->uart_offset); ufstat = inb(S3C44B0X_UFSTAT0 + info->uart_offset); }while(!(ustat & S3C44B0X_UTRSTAT_TSE) || (ufstat & S3C44B0X_UFSTAT_TX_FIFO_COUNT));}/******************************************************************************/static _INLINE_ void tx_delay(void){#if 0 volatile int i; for (i=0; i < 50; i++) { i = i+1; i = i-1; };#endif}static _INLINE_ void tx_start(struct s3c44b0x_serial *info){ if (info->use_ints && info->xmit_cnt) enable_irq(info->irq);}static _INLINE_ void tx_stop(struct s3c44b0x_serial *info){ disable_irq(info->irq);}static _INLINE_ void rx_start(struct s3c44b0x_serial *info){ if (info->use_ints) { enable_irq(info->irq_rx); enable_irq(S3C44B0X_INTERRUPT_UERR); }}static _INLINE_ void rx_stop(struct s3c44b0x_serial *info){ disable_irq(info->irq_rx); /* the error-interrupt is shared -> check if the other uart needs it */ if (((s3c44b0x_info[0].count == 1) && (s3c44b0x_info[1].count == 0)) || ((s3c44b0x_info[0].count == 0) && (s3c44b0x_info[1].count == 1)) || ((s3c44b0x_info[0].count == 0) && (s3c44b0x_info[1].count == 0))) disable_irq(S3C44B0X_INTERRUPT_UERR);}static void set_ints_mode(struct s3c44b0x_serial *info, int yes){ info->use_ints = yes; if (yes) { s3c44b0x_unmask_irq(info->irq); s3c44b0x_unmask_irq(info->irq_rx); s3c44b0x_unmask_irq(S3C44B0X_INTERRUPT_UERR); } else { /* the error-interrupt is shared -> check if the other uart needs it */ if (((s3c44b0x_info[0].count == 1) && (s3c44b0x_info[1].count == 0)) || ((s3c44b0x_info[0].count == 0) && (s3c44b0x_info[1].count == 1)) || ((s3c44b0x_info[0].count == 0) && (s3c44b0x_info[1].count == 0))) s3c44b0x_mask_irq(S3C44B0X_INTERRUPT_UERR); s3c44b0x_mask_irq(info->irq_rx); s3c44b0x_mask_irq(info->irq); }}static _INLINE_ void fifo_reset(struct s3c44b0x_serial *info){ u_int8_t ufcon = inb(S3C44B0X_UFCON0 + info->uart_offset); ufcon |= S3C44B0X_UFCON_RX_FIFO_RST | S3C44B0X_UFCON_TX_FIFO_RST; outb(ufcon, S3C44B0X_UFCON0 + info->uart_offset);}static _INLINE_ void fifo_init(struct s3c44b0x_serial *info){ u_int8_t ufcon = inb(S3C44B0X_UFCON0 + info->uart_offset); if (RX_FIFO_LEVEL + TX_FIFO_LEVEL) ufcon |= S3C44B0X_UFCON_FIFO_EN | S3C44B0X_UCON_TXINT_LEVEL | S3C44B0X_UCON_RXINT_LEVEL; switch (RX_FIFO_LEVEL) { case 4 : ufcon |= S3C44B0X_UFCON_RX_FIFO_4; break; case 8 : ufcon |= S3C44B0X_UFCON_RX_FIFO_8; break; case 12: ufcon |= S3C44B0X_UFCON_RX_FIFO_12; break; case 16: ufcon |= S3C44B0X_UFCON_RX_FIFO_16; break; } switch (TX_FIFO_LEVEL) { case 0 : ufcon |= S3C44B0X_UFCON_TX_FIFO_0; break; case 4 : ufcon |= S3C44B0X_UFCON_TX_FIFO_4; break; case 8 : ufcon |= S3C44B0X_UFCON_TX_FIFO_8; break; case 12 : ufcon |= S3C44B0X_UFCON_TX_FIFO_12; break; } outb(ufcon, S3C44B0X_UFCON0 + info->uart_offset);}static inline int serial_paranoia_check(struct s3c44b0x_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 s3c44b0x_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;}/* Sets or clears DTR on the requested line */static inline void set_dtr(struct s3c44b0x_serial *info, int set){ /* we do not need hw flow control */}/* Sets or clears RTS on the requested line *//* Note: rts is only directly controllable if hw flow * control is not enabled */static inline void set_rts(struct s3c44b0x_serial *info, int set){ /* we do not need hw flow control */}/* Reads value of serial status signals */static unsigned int get_status(struct s3c44b0x_serial *info){ /* we do not need hw flow control */ 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 s3c44b0x_serial *info = (struct s3c44b0x_serial *) tty->driver_data; unsigned long flags = 0; if (serial_paranoia_check(info, tty->device, "rs_stop")) return; save_flags(flags); cli(); tx_stop(info); rx_stop(info); restore_flags(flags);}#if !defined(CONFIG_CONSOLE_NULL)static void rs_put_char(struct s3c44b0x_serial *info, char ch){ unsigned long flags = 0; save_flags(flags); cli(); xmit_char(info, ch); restore_flags(flags);}#endifstatic void rs_start(struct tty_struct *tty){ struct s3c44b0x_serial *info = (struct s3c44b0x_serial *) tty->driver_data; unsigned long flags = 0; if (serial_paranoia_check(info, tty->device, "rs_start")) return; save_flags(flags); cli(); rx_start(info); tx_start(info); restore_flags(flags);}/* * ---------------------------------------------------------------------- * * 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_s3c44b0x_sched_event(struct s3c44b0x_serial *info, int event){ info->event |= 1 << event; queue_task_irq_off(&info->tqueue, &tq_s3c44b0x_serial); mark_bh(SERIAL_BH);}/* * This is the serial driver's generic interrupt routine */ static void rs_s3c44b0x_interruptTxa(int irq, void *dev_id, struct pt_regs *regs){ rs_s3c44b0x_interruptTx(irq, dev_id, regs, &s3c44b0x_info[1]);}static void rs_s3c44b0x_interruptTxb(int irq, void *dev_id, struct pt_regs *regs){ rs_s3c44b0x_interruptTx(irq, dev_id, regs, &s3c44b0x_info[0]);}static void rs_s3c44b0x_interruptTx(int irq, void *dev_id, struct pt_regs *regs, struct s3c44b0x_serial *serial_info){ unsigned int count, status; struct s3c44b0x_serial *info = serial_info; if (info->x_char) { xmit_char(info, info->x_char); info->x_char = 0; return; } if ((info->xmit_cnt <= 0) || info->tty->stopped || info->tty->hw_stopped ) { s3c44b0x_mask_ack_irq(info->irq); tx_stop(info); /* set the settings for receiving */ switch (info->ser_mode){ case SERIAL_MODE_RS422: case SERIAL_MODE_RS422_LISTEN: /* enable tx and rx */ tx_enable(info); rx_enable(info); break; case SERIAL_MODE_RS485_ECHO: case SERIAL_MODE_RS485_NO_ECHO: /* disable tx and enable rx */ tx_disable(info); rx_enable(info); break; case SERIAL_MODE_NONE: /* disable tx and rx */ tx_disable(info); rx_disable(info); break; default: /* do nothing */ break; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -