📄 68328serial.c
字号:
/* 68328serial.c: Serial port driver for 68328 microcontroller * * Copyright (C) 1995 David S. Miller <davem@caip.rutgers.edu> * Copyright (C) 1998 Kenneth Albanowski <kjahds@kjahds.com> * Copyright (C) 1998, 1999 D. Jeff Dionne <jeff@uclinux.org> * Copyright (C) 1999 Vladimir Gurevich <vgurevic@cisco.com> * * VZ Support/Fixes Evan Stawnyczy <e@lineo.ca> * Multiple UART support Daniel Potts <danielp@cse.unsw.edu.au> */ #include <asm/dbg.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/config.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/reboot.h>#include <linux/keyboard.h>#include <linux/init.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/system.h>#include <asm/segment.h>#include <asm/bitops.h>#include <asm/delay.h>#include <asm/uaccess.h>/* (es) *//* note: perhaps we can murge these files, so that you can just * define 1 of them, and they can sort that out for themselves */#if defined(CONFIG_M68EZ328)#include <asm/MC68EZ328.h>#else#if defined(CONFIG_M68VZ328)#include <asm/MC68VZ328.h>#else#include <asm/MC68328.h>#endif /* CONFIG_M68VZ328 */#endif /* CONFIG_M68EZ328 */#include "68328serial.h"/* Turn off usage of real serial interrupt code, to "support" Copilot */#ifdef CONFIG_XCOPILOT_BUGS#undef USE_INTS#else#define USE_INTS#endifstatic struct m68k_serial m68k_soft[NR_PORTS];struct m86k_serial *IRQ_ports[NR_IRQS];static unsigned int uart_irqs[NR_PORTS] = UART_IRQ_DEFNS;/* multiple ports are contiguous in memory */m68328_uart *uart_addr = USTCNT_ADDR;struct tty_struct m68k_ttys;struct m68k_serial *m68k_consinfo = 0;#define M68K_CLOCK (16667000) /* FIXME: 16MHz is likely wrong */DECLARE_TASK_QUEUE(tq_serial);#ifdef CONFIG_CONSOLEextern wait_queue_head_t keypress_wait; #endifstruct tty_driver serial_driver, callout_driver;static int serial_refcount;/* 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_ inlinestatic void change_speed(struct m68k_serial *info);static struct tty_struct *serial_table[NR_PORTS];static struct termios *serial_termios[NR_PORTS];static struct termios *serial_termios_locked[NR_PORTS];#ifndef MIN#define MIN(a,b) ((a) < (b) ? (a) : (b))#endif/* * Setup for console. Argument comes from the boot command line. */#if defined(CONFIG_M68EZ328ADS) || defined(CONFIG_ALMA_ANS)#define CONSOLE_BAUD_RATE 115200#define DEFAULT_CBAUD B115200#endif/* (es) *//* note: this is messy, but it works, again, perhaps defined somewhere else?*/#ifdef CONFIG_M68VZ328#define CONSOLE_BAUD_RATE 19200#define DEFAULT_CBAUD B19200#endif/* (/es) */#ifndef CONSOLE_BAUD_RATE#define CONSOLE_BAUD_RATE 9600#define DEFAULT_CBAUD B9600#endifstatic int m68328_console_initted = 0;static int m68328_console_baud = CONSOLE_BAUD_RATE;static int m68328_console_cbaud = DEFAULT_CBAUD;/* * 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 inline int serial_paranoia_check(struct m68k_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 m68k_serial 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;}/* * This is used to figure out the divisor speeds and the timeouts */static int baud_table[] = { 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 0 };#define BAUD_TABLE_SIZE (sizeof(baud_table)/sizeof(baud_table[0]))/* Sets or clears DTR/RTS on the requested line */static inline void m68k_rtsdtr(struct m68k_serial *ss, int set){ if (set) { /* set the RTS/CTS line */ } else { /* clear it */ } return;}/* Utility routines */static inline int get_baud(struct m68k_serial *ss){ unsigned long result = 115200; unsigned short int baud = uart_addr[ss->line].ubaud; if (GET_FIELD(baud, UBAUD_PRESCALER) == 0x38) result = 38400; result >>= GET_FIELD(baud, UBAUD_DIVIDE); return result;}/* * ------------------------------------------------------------ * 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 m68k_serial *info = (struct m68k_serial *)tty->driver_data; m68328_uart *uart = &uart_addr[info->line]; unsigned long flags; if (serial_paranoia_check(info, tty->device, "rs_stop")) return; save_flags(flags); cli(); uart->ustcnt &= ~USTCNT_TXEN; restore_flags(flags);}static void rs_put_char(char ch){ int flags, loops = 0; save_flags(flags); cli(); while (!(UTX & UTX_TX_AVAIL) && (loops < 1000)) { loops++; udelay(5); } UTX_TXDATA = ch; udelay(5); restore_flags(flags);}static void rs_start(struct tty_struct *tty){ struct m68k_serial *info = (struct m68k_serial *)tty->driver_data; m68328_uart *uart = &uart_addr[info->line]; unsigned long flags; if (serial_paranoia_check(info, tty->device, "rs_start")) return; save_flags(flags); cli(); if (info->xmit_cnt && info->xmit_buf && !(uart->ustcnt & USTCNT_TXEN)) {#ifdef USE_INTS uart->ustcnt |= USTCNT_TXEN | USTCNT_TX_INTR_MASK;#else uart->ustcnt |= USTCNT_TXEN;#endif } 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){ /* Drop into the debugger */}static _INLINE_ void status_handle(struct m68k_serial *info, unsigned short 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->m68k_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->m68k_channel, 3, info->curregs[3]); } }#endif /* If this is console input and this is a * 'break asserted' status change interrupt * see if we can drop into the debugger */ if((status & URX_BREAK) && info->break_abort) batten_down_hatches(); return;}/* * 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 m68k_serial *info, int event){ info->event |= 1 << event; queue_task(&info->tqueue, &tq_serial); mark_bh(SERIAL_BH);}static _INLINE_ void receive_chars(struct m68k_serial *info, struct pt_regs *regs, unsigned short rx){ struct tty_struct *tty = info->tty; m68328_uart *uart = &uart_addr[info->line]; unsigned char ch; /* * This do { } while() loop will get ALL chars out of Rx FIFO */#ifndef CONFIG_XCOPILOT_BUGS do {#endif ch = GET_FIELD(rx, URX_RXDATA); if(info->is_cons) { if(URX_BREAK & rx) { /* whee, break received */ status_handle(info, rx); return;#ifdef CONFIG_MAGIC_SYSRQ } else if (ch == 0x10) { /* ^P */ show_state(); show_free_areas(); show_buffers();/* show_net_buffers(); */ return; } else if (ch == 0x12) { /* ^R */ machine_restart(NULL); return;#endif /* CONFIG_MAGIC_SYSRQ */ } /* It is a 'keyboard interrupt' ;-) */#ifdef CONFIG_CONSOLE wake_up(&keypress_wait);#endif } if(!tty) goto clear_and_exit; /* * Make sure that we do not overflow the buffer */ if (tty->flip.count >= TTY_FLIPBUF_SIZE) { queue_task(&tty->flip.tqueue, &tq_timer); return; } if(rx & URX_PARITY_ERROR) { *tty->flip.flag_buf_ptr++ = TTY_PARITY; status_handle(info, rx); } else if(rx & URX_OVRUN) { *tty->flip.flag_buf_ptr++ = TTY_OVERRUN; status_handle(info, rx); } else if(rx & URX_FRAME_ERROR) { *tty->flip.flag_buf_ptr++ = TTY_FRAME; status_handle(info, rx); } else { *tty->flip.flag_buf_ptr++ = 0; /* XXX */ } *tty->flip.char_buf_ptr++ = ch; tty->flip.count++;#ifndef CONFIG_XCOPILOT_BUGS } while((rx = uart->urx.w) & URX_DATA_READY);#endif queue_task(&tty->flip.tqueue, &tq_timer);clear_and_exit: return;}static _INLINE_ void transmit_chars(struct m68k_serial *info){ m68328_uart *uart = &uart_addr[info->line]; if (info->x_char) { /* Send next char */ uart->utx.b.txdata = info->x_char; info->x_char = 0; goto clear_and_return; } if((info->xmit_cnt <= 0) || info->tty->stopped) { /* That's peculiar... TX ints off */ uart->ustcnt &= ~USTCNT_TX_INTR_MASK; goto clear_and_return; } /* Send char */ uart->utx.b.txdata = info->xmit_buf[info->xmit_tail++]; info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1); info->xmit_cnt--; if (info->xmit_cnt < WAKEUP_CHARS) rs_sched_event(info, RS_EVENT_WRITE_WAKEUP); if(info->xmit_cnt <= 0) { /* All done for now... TX ints off */ uart->ustcnt &= ~USTCNT_TX_INTR_MASK; goto clear_and_return; }clear_and_return: /* Clear interrupt (should be auto)*/ return;}/* * This is the serial driver's generic interrupt routine */void rs_interrupt(int irq, void *dev_id, struct pt_regs * regs){ struct m68k_serial * info; m68328_uart *uart; unsigned short rx; unsigned short tx; info = IRQ_ports[irq]; if(!info) return; uart = &uart_addr[info->line]; rx = uart->urx.w;#ifdef USE_INTS tx = uart->utx.w; if (rx & URX_DATA_READY) receive_chars(info, regs, rx); if (tx & UTX_TX_AVAIL) transmit_chars(info);#else receive_chars(info, regs, rx); #endif return;}/* * This routine is used to handle the "bottom half" processing for the * serial driver, known also the "software interrupt" processing. * This processing is done at the kernel interrupt level, after the * rs_interrupt() has returned, BUT WITH INTERRUPTS TURNED ON. This * is where time-consuming activities which can not be done in the * interrupt driver proper are done; the interrupt driver schedules * them using rs_sched_event(), and they get done here. */static void do_serial_bh(void){ run_task_queue(&tq_serial);}static void do_softint(void *private_){ struct m68k_serial *info = (struct m68k_serial *) private_; struct tty_struct *tty; tty = info->tty; if (!tty) return;#if 0 if (clear_bit(RS_EVENT_WRITE_WAKEUP, &info->event)) { if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup) (tty->ldisc.write_wakeup)(tty); wake_up_interruptible(&tty->write_wait); }#endif }/* * This routine is called from the scheduler tqueue when the interrupt * routine has signalled that a hangup has occurred. The path of * hangup processing is: * * serial interrupt routine -> (scheduler tqueue) -> * do_serial_hangup() -> tty->hangup() -> rs_hangup() * */static void do_serial_hangup(void *private_){ struct m68k_serial *info = (struct m68k_serial *) private_; struct tty_struct *tty; tty = info->tty; if (!tty) return; tty_hangup(tty);}static int startup(struct m68k_serial * info){ m68328_uart *uart = &uart_addr[info->line]; unsigned long flags; if (info->flags & S_INITIALIZED) return 0; if (!info->xmit_buf) { info->xmit_buf = (unsigned char *) get_free_page(GFP_KERNEL); if (!info->xmit_buf) return -ENOMEM; } save_flags(flags); cli(); /* * Clear the FIFO buffers and disable them * (they will be reenabled in change_speed()) */ uart->ustcnt = USTCNT_UEN; info->xmit_fifo_size = 1; uart->ustcnt = USTCNT_UEN | USTCNT_RXEN | USTCNT_TXEN; (void)uart->urx.w; /* * Finally, enable sequencing and interrupts */#ifdef USE_INTS uart->ustcnt = USTCNT_UEN | USTCNT_RXEN | USTCNT_RX_INTR_MASK | USTCNT_TX_INTR_MASK;#else uart->ustcnt = USTCNT_UEN | USTCNT_RXEN | USTCNT_RX_INTR_MASK;#endif if (info->tty) clear_bit(TTY_IO_ERROR, &info->tty->flags); info->xmit_cnt = info->xmit_head = info->xmit_tail = 0; /* * and set the speed of the serial port */ change_speed(info); info->flags |= S_INITIALIZED; restore_flags(flags); return 0;}/* * This routine will shutdown a serial port; interrupts are disabled, and * DTR is dropped if the hangup on close termio flag is on. */static void shutdown(struct m68k_serial * info){
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -