📄 serial167.c
字号:
/* * linux/drivers/char/serial167.c * * Driver for MVME166/7 board serial ports, which are via a CD2401. * Based very much on cyclades.c. * * MVME166/7 work by Richard Hirst [richard@sleepie.demon.co.uk] * * ============================================================== * * static char rcsid[] = * "$Revision: 1.36.1.4 $$Date: 1995/03/29 06:14:14 $"; * * linux/kernel/cyclades.c * * Maintained by Marcio Saito (cyclades@netcom.com) and * Randolph Bentson (bentson@grieg.seaslug.org) * * Much of the design and some of the code came from serial.c * which was copyright (C) 1991, 1992 Linus Torvalds. It was * extensively rewritten by Theodore Ts'o, 8/16/92 -- 9/14/92, * and then fixed as suggested by Michael K. Johnson 12/12/92. * * This version does not support shared irq's. * * This module exports the following rs232 io functions: * int cy_init(void); * int cy_open(struct tty_struct *tty, struct file *filp); * * $Log: cyclades.c,v $ * Revision 1.36.1.4 1995/03/29 06:14:14 bentson * disambiguate between Cyclom-16Y and Cyclom-32Ye; * * 200 lines of changes record removed - RGH 11-10-95, starting work on * converting this to drive serial ports on mvme166 (cd2401). */#include <linux/config.h>#include <linux/errno.h>#include <linux/signal.h>#include <linux/sched.h>#include <linux/timer.h>#include <linux/tty.h>#include <linux/interrupt.h>#include <linux/serial.h>#include <linux/string.h>#include <linux/fcntl.h>#include <linux/ptrace.h>#include <linux/serial167.h>#include <linux/delay.h>#include <linux/major.h>#include <linux/mm.h>#include <linux/console.h>#include <asm/system.h>#include <asm/io.h>#include <asm/segment.h>#include <asm/bitops.h>#include <asm/mvme16xhw.h>#include <linux/types.h>#include <linux/kernel.h>#include <linux/version.h>#include <asm/uaccess.h>#include <linux/init.h>#define cy_put_user put_userstatic unsigned long cy_get_user(unsigned long *addr){ unsigned long result = 0; int error = get_user (result, addr); if (error) printk ("serial167: cy_get_user: error == %d\n", error); return result;}#define SERIAL_PARANOIA_CHECK#undef SERIAL_DEBUG_OPEN#undef SERIAL_DEBUG_THROTTLE#undef SERIAL_DEBUG_OTHER#undef SERIAL_DEBUG_IO#undef SERIAL_DEBUG_COUNT#undef SERIAL_DEBUG_DTR#undef CYCLOM_16Y_HACK#define CYCLOM_ENABLE_MONITORING#ifndef MIN#define MIN(a,b) ((a) < (b) ? (a) : (b))#endif#define WAKEUP_CHARS 256#define STD_COM_FLAGS (0)#define SERIAL_TYPE_NORMAL 1#define SERIAL_TYPE_CALLOUT 2DECLARE_TASK_QUEUE(tq_cyclades);struct tty_driver cy_serial_driver, cy_callout_driver;extern int serial_console;static struct cyclades_port *serial_console_info = NULL;static unsigned int serial_console_cflag = 0;u_char initial_console_speed;/* Base address of cd2401 chip on mvme166/7 */#define BASE_ADDR (0xfff45000)#define pcc2chip ((volatile u_char *)0xfff42000)#define PccSCCMICR 0x1d#define PccSCCTICR 0x1e#define PccSCCRICR 0x1f#define PccTPIACKR 0x25#define PccRPIACKR 0x27#define PccIMLR 0x3f/* This is the per-port data structure */struct cyclades_port cy_port[] = { /* CARD# */ {-1 }, /* ttyS0 */ {-1 }, /* ttyS1 */ {-1 }, /* ttyS2 */ {-1 }, /* ttyS3 */};#define NR_PORTS (sizeof(cy_port)/sizeof(struct cyclades_port))static int serial_refcount;static struct tty_struct *serial_table[NR_PORTS];static struct termios *serial_termios[NR_PORTS];static struct termios *serial_termios_locked[NR_PORTS];/* * 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 *tmp_buf = 0;static struct semaphore tmp_buf_sem = MUTEX;/* * This is used to look up the divisor speeds and the timeouts * We're normally limited to 15 distinct baud rates. The extra * are accessed via settings in info->flags. * 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, * 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, * HI VHI */static int baud_table[] = { 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 76800,115200,150000, 0};#if 0static char baud_co[] = { /* 25 MHz clock option table */ /* value => 00 01 02 03 04 */ /* divide by 8 32 128 512 2048 */ 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};static char baud_bpr[] = { /* 25 MHz baud rate period table */ 0x00, 0xf5, 0xa3, 0x6f, 0x5c, 0x51, 0xf5, 0xa3, 0x51, 0xa3, 0x6d, 0x51, 0xa3, 0x51, 0xa3, 0x51, 0x36, 0x29, 0x1b, 0x15};#endif/* I think 166 brd clocks 2401 at 20MHz.... *//* These values are written directly to tcor, and >> 5 for writing to rcor */static u_char baud_co[] = { /* 20 MHz clock option table */ 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x60, 0x60, 0x40, 0x40, 0x40, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};/* These values written directly to tbpr/rbpr */static u_char baud_bpr[] = { /* 20 MHz baud rate period table */ 0x00, 0xc0, 0x80, 0x58, 0x6c, 0x40, 0xc0, 0x81, 0x40, 0x81, 0x57, 0x40, 0x81, 0x40, 0x81, 0x40, 0x2b, 0x20, 0x15, 0x10};static u_char baud_cor4[] = { /* receive threshold */ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x08, 0x08, 0x08, 0x08, 0x07};static void shutdown(struct cyclades_port *);static int startup (struct cyclades_port *);static void cy_throttle(struct tty_struct *);static void cy_unthrottle(struct tty_struct *);static void config_setup(struct cyclades_port *);extern void console_print(const char *);#ifdef CYCLOM_SHOW_STATUSstatic void show_status(int);#endif#ifdef CONFIG_REMOTE_DEBUGstatic void debug_setup(void);void queueDebugChar (int c);int getDebugChar(void);#define DEBUG_PORT 1#define DEBUG_LEN 256typedef struct { int in; int out; unsigned char buf[DEBUG_LEN];} debugq;debugq debugiq;#endif/* * I have my own version of udelay(), as it is needed when initialising * the chip, before the delay loop has been calibrated. Should probably * reference one of the vmechip2 or pccchip2 counter for an accurate * delay, but this wild guess will do for now. */void my_udelay (long us){ u_char x; volatile u_char *p = &x; int i; while (us--) for (i = 100; i; i--) x |= *p;}static inline intserial_paranoia_check(struct cyclades_port *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 cyclades_port for (%d, %d) in %s\n"; static const char *badrange = "Warning: cyclades_port out of range for (%d, %d) in %s\n"; if (!info) { printk(badinfo, MAJOR(device), MINOR(device), routine); return 1; } if( (long)info < (long)(&cy_port[0]) || (long)(&cy_port[NR_PORTS]) < (long)info ){ printk(badrange, MAJOR(device), MINOR(device), routine); return 1; } if (info->magic != CYCLADES_MAGIC) { printk(badmagic, MAJOR(device), MINOR(device), routine); return 1; }#endif return 0;} /* serial_paranoia_check */#if 0/* The following diagnostic routines allow the driver to spew information on the screen, even (especially!) during interrupts. */voidSP(char *data){ unsigned long flags; save_flags(flags); cli(); console_print(data); restore_flags(flags);}char scrn[2];voidCP(char data){ unsigned long flags; save_flags(flags); cli(); scrn[0] = data; console_print(scrn); restore_flags(flags);}/* CP */void CP1(int data) { (data<10)? CP(data+'0'): CP(data+'A'-10); }/* CP1 */void CP2(int data) { CP1((data>>4) & 0x0f); CP1( data & 0x0f); }/* CP2 */void CP4(int data) { CP2((data>>8) & 0xff); CP2(data & 0xff); }/* CP4 */void CP8(long data) { CP4((data>>16) & 0xffff); CP4(data & 0xffff); }/* CP8 */#endif/* This routine waits up to 1000 micro-seconds for the previous command to the Cirrus chip to complete and then issues the new command. An error is returned if the previous command didn't finish within the time limit. */u_shortwrite_cy_cmd(volatile u_char *base_addr, u_char cmd){ unsigned long flags; volatile int i; save_flags(flags); cli(); /* Check to see that the previous command has completed */ for(i = 0 ; i < 100 ; i++){ if (base_addr[CyCCR] == 0){ break; } my_udelay(10L); } /* if the CCR never cleared, the previous command didn't finish within the "reasonable time" */ if ( i == 10 ) { restore_flags(flags); return (-1); } /* Issue the new command */ base_addr[CyCCR] = cmd; restore_flags(flags); return(0);} /* write_cy_cmd *//* cy_start and cy_stop provide software output flow control as a function of XON/XOFF, software CTS, and other such stuff. */static voidcy_stop(struct tty_struct *tty){ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data; volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR; int channel; unsigned long flags;#ifdef SERIAL_DEBUG_OTHER printk("cy_stop ttyS%d\n", info->line); /* */#endif if (serial_paranoia_check(info, tty->device, "cy_stop")) return; channel = info->line; save_flags(flags); cli(); base_addr[CyCAR] = (u_char)(channel); /* index channel */ base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy); restore_flags(flags); return;} /* cy_stop */static voidcy_start(struct tty_struct *tty){ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data; volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR; int channel; unsigned long flags;#ifdef SERIAL_DEBUG_OTHER printk("cy_start ttyS%d\n", info->line); /* */#endif if (serial_paranoia_check(info, tty->device, "cy_start")) return; channel = info->line; save_flags(flags); cli(); base_addr[CyCAR] = (u_char)(channel); base_addr[CyIER] |= CyTxMpty; restore_flags(flags); return;} /* cy_start *//* * This routine is used by the interrupt handler to schedule * processing in the software interrupt portion of the driver * (also known as the "bottom half"). This can be called any * number of times for any channel without harm. */static inline voidcy_sched_event(struct cyclades_port *info, int event){ info->event |= 1 << event; /* remember what kind of event and who */ queue_task(&info->tqueue, &tq_cyclades); /* it belongs to */ mark_bh(CYCLADES_BH); /* then trigger event */} /* cy_sched_event *//* The real interrupt service routines are called whenever the card wants its hand held--chars received, out buffer empty, modem change, etc. */static voidcd2401_rxerr_interrupt(int irq, void *dev_id, struct pt_regs *fp){ struct tty_struct *tty; struct cyclades_port *info; volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR; unsigned char err, rfoc; int channel; char data; /* determine the channel and change to that context */ channel = (u_short ) (base_addr[CyLICR] >> 2); info = &cy_port[channel]; info->last_active = jiffies; if ((err = base_addr[CyRISR]) & CyTIMEOUT) { /* This is a receive timeout interrupt, ignore it */ base_addr[CyREOIR] = CyNOTRANS; return; } /* Read a byte of data if there is any - assume the error * is associated with this character */ if ((rfoc = base_addr[CyRFOC]) != 0) data = base_addr[CyRDR]; else data = 0; /* if there is nowhere to put the data, discard it */ if(info->tty == 0) { base_addr[CyREOIR] = rfoc ? 0 : CyNOTRANS; return; } else { /* there is an open port for this data */ tty = info->tty; if(err & info->ignore_status_mask){ base_addr[CyREOIR] = rfoc ? 0 : CyNOTRANS; return; } if (tty->flip.count < TTY_FLIPBUF_SIZE){ tty->flip.count++; if (err & info->read_status_mask){ if(err & CyBREAK){ *tty->flip.flag_buf_ptr++ = TTY_BREAK; *tty->flip.char_buf_ptr++ = data; if (info->flags & ASYNC_SAK){ do_SAK(tty); } }else if(err & CyFRAME){ *tty->flip.flag_buf_ptr++ = TTY_FRAME; *tty->flip.char_buf_ptr++ = data; }else if(err & CyPARITY){ *tty->flip.flag_buf_ptr++ = TTY_PARITY; *tty->flip.char_buf_ptr++ = data; }else if(err & CyOVERRUN){ *tty->flip.flag_buf_ptr++ = TTY_OVERRUN; *tty->flip.char_buf_ptr++ = 0; /* If the flip buffer itself is overflowing, we still loose the next incoming character. */ if(tty->flip.count < TTY_FLIPBUF_SIZE){ tty->flip.count++; *tty->flip.flag_buf_ptr++ = TTY_NORMAL; *tty->flip.char_buf_ptr++ = data; } /* These two conditions may imply */ /* a normal read should be done. */ /* else if(data & CyTIMEOUT) */ /* else if(data & CySPECHAR) */ }else{ *tty->flip.flag_buf_ptr++ = 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -