📄 uart.c
字号:
/* * UART driver for MPC860 CPM SCC or SMC * Copyright (c) 1997 Dan Malek (dmalek@jlc.net) * * I used the serial.c driver as the framework for this driver. * Give credit to those guys. * The original code was written for the MBX860 board. I tried to make * it generic, but there may be some assumptions in the structures that * have to be fixed later. * To save porting time, I did not bother to change any object names * that are not accessed outside of this file. * It still needs lots of work........When it was easy, I included code * to support the SCCs, but this has never been tested, nor is it complete. * Only the SCCs support modem control, so that is not complete either. * * This module exports the following rs232 io functions: * * int rs_8xx_init(void); */#include <linux/config.h>#include <linux/module.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/serial.h>#include <linux/serialP.h>#include <linux/major.h>#include <linux/string.h>#include <linux/fcntl.h>#include <linux/ptrace.h>#include <linux/mm.h>#include <linux/malloc.h>#include <linux/init.h>#include <linux/delay.h>#include <asm/uaccess.h>#include <asm/8xx_immap.h>#include <asm/mpc8xx.h>#include "commproc.h"#ifdef CONFIG_KGDBextern void breakpoint(void);extern void set_debug_traps(void);extern int kgdb_output_string (const char* s, unsigned int count);#endif#ifdef CONFIG_SERIAL_CONSOLE#include <linux/console.h>/* this defines the index into rs_table for the port to use*/#ifndef CONFIG_SERIAL_CONSOLE_PORT#define CONFIG_SERIAL_CONSOLE_PORT 0#endif#endif#if 0/* SCC2 for console*/#undef CONFIG_SERIAL_CONSOLE_PORT#define CONFIG_SERIAL_CONSOLE_PORT 2#endif#define TX_WAKEUP ASYNC_SHARE_IRQstatic char *serial_name = "CPM UART driver";static char *serial_version = "0.03";static DECLARE_TASK_QUEUE(tq_serial);static struct tty_driver serial_driver, callout_driver;static int serial_refcount;static int serial_console_setup(struct console *co, char *options);/* * Serial driver configuration section. Here are the various options: */#define SERIAL_PARANOIA_CHECK#define CONFIG_SERIAL_NOPAUSE_IO#define SERIAL_DO_RESTART/* Set of debugging defines */#undef SERIAL_DEBUG_INTR#undef SERIAL_DEBUG_OPEN#undef SERIAL_DEBUG_FLOW#undef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT#define _INLINE_ inline #define DBG_CNT(s)/* We overload some of the items in the data structure to meet our * needs. For example, the port address is the CPM parameter ram * offset for the SCC or SMC. The maximum number of ports is 4 SCCs and * 2 SMCs. The "hub6" field is used to indicate the channel number, with * a flag indicating SCC or SMC, and the number is used as an index into * the CPM parameter area for this device. * The "type" field is currently set to 0, for PORT_UNKNOWN. It is * not currently used. I should probably use it to indicate the port * type of SMC or SCC. * The SMCs do not support any modem control signals. */#define smc_scc_num hub6#define NUM_IS_SCC ((int)0x00010000)#define PORT_NUM(P) ((P) & 0x0000ffff)/* Processors other than the 860 only get SMCs configured by default. * Either they don't have SCCs or they are allocated somewhere else. * Of course, there are now 860s without some SCCs, so we will need to * address that someday. * The Embedded Planet Multimedia I/O cards use TDM interfaces to the * stereo codec parts, and we use SMC2 to help support that. */static struct serial_state rs_table[] = { /* UART CLK PORT IRQ FLAGS NUM */ { 0, 0, PROFF_SMC1, CPMVEC_SMC1, 0, 0 }, /* SMC1 ttyS0 */#ifdef CONFIG_8xxSMC2 { 0, 0, PROFF_SMC2, CPMVEC_SMC2, 0, 1 }, /* SMC2 ttyS1 */#endif#ifdef CONFIG_8xxSCC { 0, 0, PROFF_SCC2, CPMVEC_SCC2, 0, (NUM_IS_SCC | 1) }, /* SCC2 ttyS2 */ { 0, 0, PROFF_SCC3, CPMVEC_SCC3, 0, (NUM_IS_SCC | 2) }, /* SCC3 ttyS3 */#endif};#define NR_PORTS (sizeof(rs_table)/sizeof(struct serial_state))static struct tty_struct *serial_table[NR_PORTS];static struct termios *serial_termios[NR_PORTS];static struct termios *serial_termios_locked[NR_PORTS];/* The number of buffer descriptors and their sizes.*/#define RX_NUM_FIFO 4#define RX_BUF_SIZE 32#define TX_NUM_FIFO 4#define TX_BUF_SIZE 32#ifndef MIN#define MIN(a,b) ((a) < (b) ? (a) : (b))#endif/* The async_struct in serial.h does not really give us what we * need, so define our own here. */typedef struct serial_info { int magic; int flags; struct serial_state *state; struct tty_struct *tty; int read_status_mask; int ignore_status_mask; int timeout; int line; int x_char; /* xon/xoff character */ int close_delay; unsigned short closing_wait; unsigned short closing_wait2; unsigned long event; unsigned long last_active; int blocked_open; /* # of blocked opens */ long session; /* Session of opening process */ long pgrp; /* pgrp of opening process */ struct tq_struct tqueue; struct tq_struct tqueue_hangup; wait_queue_head_t open_wait; wait_queue_head_t close_wait; /* CPM Buffer Descriptor pointers. */ cbd_t *rx_bd_base; cbd_t *rx_cur; cbd_t *tx_bd_base; cbd_t *tx_cur;} ser_info_t;static void change_speed(ser_info_t *info);static void rs_8xx_wait_until_sent(struct tty_struct *tty, int timeout);static inline int serial_paranoia_check(ser_info_t *info, kdev_t device, const char *routine){#ifdef SERIAL_PARANOIA_CHECK static const char *badmagic = "Warning: bad magic number for serial struct (%s) in %s\n"; static const char *badinfo = "Warning: null async_struct for (%s) in %s\n"; if (!info) { printk(badinfo, kdevname(device), routine); return 1; } if (info->magic != SERIAL_MAGIC) { printk(badmagic, kdevname(device), routine); return 1; }#endif return 0;}/* * This is used to figure out the divisor speeds and the timeouts, * indexed by the termio value. The generic CPM functions are responsible * for setting and assigning baud rate generators for us. */static int baud_table[] = { 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 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_8xx_stop(struct tty_struct *tty){ ser_info_t *info = (ser_info_t *)tty->driver_data; int idx; unsigned long flags; volatile scc_t *sccp; volatile smc_t *smcp; if (serial_paranoia_check(info, tty->device, "rs_stop")) return; save_flags(flags); cli(); idx = PORT_NUM(info->state->smc_scc_num); if (info->state->smc_scc_num & NUM_IS_SCC) { sccp = &cpmp->cp_scc[idx]; sccp->scc_sccm &= ~UART_SCCM_TX; } else { smcp = &cpmp->cp_smc[idx]; smcp->smc_smcm &= ~SMCM_TX; } restore_flags(flags);}static void rs_8xx_start(struct tty_struct *tty){ ser_info_t *info = (ser_info_t *)tty->driver_data; int idx; unsigned long flags; volatile scc_t *sccp; volatile smc_t *smcp; if (serial_paranoia_check(info, tty->device, "rs_stop")) return; idx = PORT_NUM(info->state->smc_scc_num); save_flags(flags); cli(); if (info->state->smc_scc_num & NUM_IS_SCC) { sccp = &cpmp->cp_scc[idx]; sccp->scc_sccm |= UART_SCCM_TX; } else { smcp = &cpmp->cp_smc[idx]; smcp->smc_smcm |= SMCM_TX; } 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 assemble 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(ser_info_t *info, int event){ info->event |= 1 << event; queue_task(&info->tqueue, &tq_serial); mark_bh(SERIAL_BH);}static _INLINE_ void receive_chars(ser_info_t *info){ struct tty_struct *tty = info->tty; unsigned char ch, *cp; /*int ignored = 0;*/ int i; ushort status; struct async_icount *icount; volatile cbd_t *bdp; icount = &info->state->icount; /* Just loop through the closed BDs and copy the characters into * the buffer. */ bdp = info->rx_cur; for (;;) { if (bdp->cbd_sc & BD_SC_EMPTY) /* If this one is empty */ break; /* we are all done */ /* The read status mask tell us what we should do with * incoming characters, especially if errors occur. * One special case is the use of BD_SC_EMPTY. If * this is not set, we are supposed to be ignoring * inputs. In this case, just mark the buffer empty and * continue. if (!(info->read_status_mask & BD_SC_EMPTY)) { bdp->cbd_sc |= BD_SC_EMPTY; bdp->cbd_sc &= ~(BD_SC_BR | BD_SC_FR | BD_SC_PR | BD_SC_OV); if (bdp->cbd_sc & BD_SC_WRAP) bdp = info->rx_bd_base; else bdp++; continue; } */ /* Get the number of characters and the buffer pointer. */ i = bdp->cbd_datlen; cp = (unsigned char *)__va(bdp->cbd_bufaddr); status = bdp->cbd_sc; /* Check to see if there is room in the tty buffer for * the characters in our BD buffer. If not, we exit * now, leaving the BD with the characters. We'll pick * them up again on the next receive interrupt (which could * be a timeout). */ if ((tty->flip.count + i) >= TTY_FLIPBUF_SIZE) break; while (i-- > 0) { ch = *cp++; *tty->flip.char_buf_ptr = ch; icount->rx++;#ifdef SERIAL_DEBUG_INTR printk("DR%02x:%02x...", ch, status);#endif *tty->flip.flag_buf_ptr = 0; if (status & (BD_SC_BR | BD_SC_FR | BD_SC_PR | BD_SC_OV)) { /* * For statistics only */ if (status & BD_SC_BR) icount->brk++; else if (status & BD_SC_PR) icount->parity++; else if (status & BD_SC_FR) icount->frame++; if (status & BD_SC_OV) icount->overrun++; /* * Now check to see if character should be * ignored, and mask off conditions which * should be ignored. if (status & info->ignore_status_mask) { if (++ignored > 100) break; continue; } */ status &= info->read_status_mask; if (status & (BD_SC_BR)) {#ifdef SERIAL_DEBUG_INTR printk("handling break....");#endif *tty->flip.flag_buf_ptr = TTY_BREAK; if (info->flags & ASYNC_SAK) do_SAK(tty); } else if (status & BD_SC_PR) *tty->flip.flag_buf_ptr = TTY_PARITY; else if (status & BD_SC_FR) *tty->flip.flag_buf_ptr = TTY_FRAME; if (status & BD_SC_OV) { /* * Overrun is special, since it's * reported immediately, and doesn't * affect the current character */ if (tty->flip.count < TTY_FLIPBUF_SIZE) { tty->flip.count++; tty->flip.flag_buf_ptr++; tty->flip.char_buf_ptr++; *tty->flip.flag_buf_ptr = TTY_OVERRUN; } } } if (tty->flip.count >= TTY_FLIPBUF_SIZE) break; tty->flip.flag_buf_ptr++; tty->flip.char_buf_ptr++; tty->flip.count++; } /* This BD is ready to be used again. Clear status. * Get next BD. */ bdp->cbd_sc |= BD_SC_EMPTY; bdp->cbd_sc &= ~(BD_SC_BR | BD_SC_FR | BD_SC_PR | BD_SC_OV); if (bdp->cbd_sc & BD_SC_WRAP) bdp = info->rx_bd_base; else bdp++; } info->rx_cur = (cbd_t *)bdp; queue_task(&tty->flip.tqueue, &tq_timer);}static _INLINE_ void receive_break(ser_info_t *info){ struct tty_struct *tty = info->tty; info->state->icount.brk++; /* Check to see if there is room in the tty buffer for * the break. If not, we exit now, losing the break. FIXME */ if ((tty->flip.count + 1) >= TTY_FLIPBUF_SIZE) return; *(tty->flip.flag_buf_ptr++) = TTY_BREAK; *(tty->flip.char_buf_ptr++) = 0; tty->flip.count++; queue_task(&tty->flip.tqueue, &tq_timer);}static _INLINE_ void transmit_chars(ser_info_t *info){ if ((info->flags & TX_WAKEUP) || (info->tty->flags & (1 << TTY_DO_WRITE_WAKEUP))) { rs_sched_event(info, RS_EVENT_WRITE_WAKEUP); }#ifdef SERIAL_DEBUG_INTR printk("THRE...");#endif}#ifdef notdef /* I need to do this for the SCCs, so it is left as a reminder. */static _INLINE_ void check_modem_status(struct async_struct *info){ int status; struct async_icount *icount; status = serial_in(info, UART_MSR); if (status & UART_MSR_ANY_DELTA) { icount = &info->state->icount; /* update input line counters */ if (status & UART_MSR_TERI) icount->rng++; if (status & UART_MSR_DDSR) icount->dsr++; if (status & UART_MSR_DDCD) { icount->dcd++;#ifdef CONFIG_HARD_PPS if ((info->flags & ASYNC_HARDPPS_CD) && (status & UART_MSR_DCD)) hardpps();#endif } if (status & UART_MSR_DCTS) icount->cts++; wake_up_interruptible(&info->delta_msr_wait); } if ((info->flags & ASYNC_CHECK_CD) && (status & UART_MSR_DDCD)) {#if (defined(SERIAL_DEBUG_OPEN) || defined(SERIAL_DEBUG_INTR)) printk("ttys%d CD now %s...", info->line, (status & UART_MSR_DCD) ? "on" : "off");#endif if (status & UART_MSR_DCD) wake_up_interruptible(&info->open_wait); else if (!((info->flags & ASYNC_CALLOUT_ACTIVE) && (info->flags & ASYNC_CALLOUT_NOHUP))) {#ifdef SERIAL_DEBUG_OPEN printk("scheduling hangup...");#endif MOD_INC_USE_COUNT;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -