📄 serial_s3c44b0x.c
字号:
return; } for (count = TX_FIFO_DEPTH; count > 0; --count) { status = inb(S3C44B0X_UFSTAT0 + info->uart_offset); if (status & S3C44B0X_UFSTAT_TX_FIFO_FULL) { handle_status (info, S3C44B0X_UFSTAT_TX_FIFO_FULL); break; } else { xmit_char(info, info->xmit_buf[info->xmit_tail++]); info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE - 1); if (--info->xmit_cnt <= 0) break; } } if (info->xmit_cnt < WAKEUP_CHARS) rs_s3c44b0x_sched_event(info, RS_EVENT_WRITE_WAKEUP); if (info->xmit_cnt <= 0) { 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; } } return;}#define S3C44B0X_UERSTAT_ERROR ( S3C44B0X_UERSTAT_OVERRUN_ERROR |\ S3C44B0X_UERSTAT_PARITY_ERROR |\ S3C44B0X_UERSTAT_FRAME_ERROR |\ S3C44B0X_UERSTAT_BREAK_DETECT )static void rs_s3c44b0x_interruptRxa(int irq, void *dev_id, struct pt_regs *regs){ if (s3c44b0x_info[1].count) rs_s3c44b0x_interruptRx(irq, dev_id, regs, &s3c44b0x_info[1]);}static void rs_s3c44b0x_interruptRxb(int irq, void *dev_id, struct pt_regs *regs){ if (s3c44b0x_info[0].count) rs_s3c44b0x_interruptRx(irq, dev_id, regs, &s3c44b0x_info[0]);}static void rs_s3c44b0x_interruptRx(int irq, void *dev_id, struct pt_regs *regs, struct s3c44b0x_serial *serial_info){ struct s3c44b0x_serial *info = serial_info; struct tty_struct *tty = info->tty; unsigned int count; volatile u_int8_t status, fifo_status; if (!info || !tty || (!(info->flags & S_INITIALIZED))) return; if ((tty->flip.count + RX_FIFO_DEPTH) >= TTY_FLIPBUF_SIZE) queue_task_irq_off(&tty->flip.tqueue, &tq_timer); count = RX_FIFO_DEPTH; do { /* check all error flags and accept data if valid */ fifo_status = inb(S3C44B0X_UFSTAT0 + info->uart_offset); if (!(fifo_status & S3C44B0X_UFSTAT_RX_FIFO_COUNT)) break; /* if 0 bytes exit */ status = inb(S3C44B0X_UERSTAT0 + info->uart_offset); if (!(status & S3C44B0X_UERSTAT_ERROR)) *tty->flip.flag_buf_ptr = TTY_NORMAL; else { if (fifo_status & S3C44B0X_UFSTAT_RX_FIFO_FULL) { *tty->flip.flag_buf_ptr = TTY_NORMAL; handle_status(info, S3C44B0X_UFSTAT_RX_FIFO_FULL); } if (status & S3C44B0X_UERSTAT_OVERRUN_ERROR) { *tty->flip.flag_buf_ptr = TTY_OVERRUN; handle_status (info, S3C44B0X_UERSTAT_OVERRUN_ERROR); } if (status & S3C44B0X_UERSTAT_BREAK_DETECT) { *tty->flip.flag_buf_ptr = TTY_BREAK; handle_status (info, S3C44B0X_UERSTAT_BREAK_DETECT); } if (status & S3C44B0X_UERSTAT_PARITY_ERROR) { *tty->flip.flag_buf_ptr = TTY_PARITY; handle_status (info, S3C44B0X_UERSTAT_PARITY_ERROR); } if (status & S3C44B0X_UERSTAT_FRAME_ERROR) { *tty->flip.flag_buf_ptr = TTY_FRAME; handle_status (info, S3C44B0X_UERSTAT_FRAME_ERROR); } } *tty->flip.char_buf_ptr++ = inb(S3C44B0X_URXH0 + info->uart_offset); tty->flip.flag_buf_ptr++; tty->flip.count++; } while ((--count > 0) && !(status & S3C44B0X_UERSTAT_ERROR));#if 0 if (fifo_status & (U_RFOV | U_E_RxTO)) handle_status (info, (U_RFOV | U_E_RxTO));#endif queue_task_irq_off(&tty->flip.tqueue, &tq_timer); }static void rs_s3c44b0x_interruptErr(int irq, void *dev_id, struct pt_regs *regs){ rs_s3c44b0x_interruptRxa(irq, dev_id, regs); rs_s3c44b0x_interruptRxb(irq, dev_id, regs);}static _INLINE_ void handle_status ( struct s3c44b0x_serial *info, unsigned int status ){#if 0 if (status & U_TFFUL) { return; /* FIXME - do something */ } if (status & U_RFFUL) { info->usart->cr |= U_SBR; } if (status & U_BSD) { batten_down_hatches(); } info->usart->csr |= status;#endif}/* * ------------------------------------------------------------------- * Here ends the serial interrupt routines. * ------------------------------------------------------------------- *//* * 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_s3c44b0x_sched_event(), and they get done here. */static void do_serial_bh(void){ run_task_queue(&tq_s3c44b0x_serial);}static void do_softint(void *private_){ struct s3c44b0x_serial *info = (struct s3c44b0x_serial *) private_; struct tty_struct *tty; tty = info->tty; if (!tty ) return; if (test_and_clear_bit(RS_EVENT_WRITE_WAKEUP, &info->event)) { wake_up_interruptible(&tty->write_wait); }}/* * 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 s3c44b0x_serial *info = (struct s3c44b0x_serial *) private_; struct tty_struct *tty; tty = info->tty; if (!tty) return; tty_hangup(tty);}static _INLINE_ u_int16_t calcCD(u_int32_t baudrate){ const u_int32_t brg_base = CONFIG_ARM_CLK >> 4; u_int32_t divisor, rest; /* * the S3C44B0X datasheet says that the baudrate has to be * calculated by ((CPU_CLOCK/16)/Baudrate)-1 * As we do not want floating point in kernel, we have to * round manually by looking at the remaining rest of the * division through the baudrate. */ divisor = brg_base / baudrate; rest = brg_base % baudrate; if (rest <= (baudrate >> 1)) divisor--; return divisor;}static _INLINE_ void uart_speed(struct s3c44b0x_serial *info, unsigned int cflag){ unsigned baud = info->baud; u_int16_t divisor = calcCD(baud); outw(divisor, S3C44B0X_UBRDIV0 + info->uart_offset);}static int startup(struct s3c44b0x_serial *info){ 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();#ifdef SERIAL_DEBUG_OPEN printk("starting up ttyS%d (irq %d)...\n", info->line, info->irq);#endif fifo_init(info); /* * Clear the FIFO buffers and disable them * (they will be reenabled in change_speed()) */ fifo_reset(info); /* * Now, initialize the UART */ 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 s3c44b0x_serial *info){ unsigned long flags; tx_stop(info); rx_stop(info); /* All off! */ if (!(info->flags & S_INITIALIZED)) return; #ifdef SERIAL_DEBUG_OPEN printk("Shutting down serial port %d (irq %d)....\n", info->line, info->irq);#endif save_flags(flags); cli(); /* Disable interrupts */ if (info->xmit_buf) { free_page((unsigned long) info->xmit_buf); info->xmit_buf = 0; } if (info->tty) set_bit(TTY_IO_ERROR, &info->tty->flags); info->flags &= ~S_INITIALIZED; restore_flags(flags);}static s3c_baud_table_t baud_table[] = { { 50, B50 }, { 75, B75 }, { 110, B110 }, { 134, B134 }, { 150, B150 }, { 200, B200 }, { 300, B300 }, { 600, B600 }, { 1200, B1200 }, { 1800, B1800 }, { 2400, B2400 }, { 4800, B4800 }, { 9600, B9600 }, { 19200, B19200 }, { 38400, B38400 }, { 57600, B57600 }, { 115200, B115200 }};/* * This routine is called to set the UART divisor registers to match * the specified baud rate for a serial port. */static void change_speed(struct s3c44b0x_serial *info){ unsigned cflag; int table_index; char ulcon, ucon; const unsigned int baudrates = sizeof(baud_table)/sizeof(baud_table[0]); if (!info->tty || !info->tty->termios) return; cflag = info->tty->termios->c_cflag; tx_stop(info); rx_stop(info); /* UART baudrate setup */ for (table_index = 0; table_index < baudrates; table_index++) { if (baud_table[table_index].cflag == (cflag & CBAUD)) {#ifdef SERIAL_DEBUG_SPEED printk("setting baudrate to %u\n", baud_table[table_index].rate);#endif break; } } if (table_index >= baudrates) { /* baudrate not found */#ifdef SERIAL_DEBUG_SPEED printk("baudrate not found, cflag=0x%08X\n", cflag);#endif /* UART control register setup */ ucon = 0; /* receive mode: IRQ or polling */ ucon |= S3C44B0X_UCON_RX_MODE_INT_POLL; /* transmit mode: IRQ or polling */ ucon |= S3C44B0X_UCON_TX_MODE_INT_POLL; /* Rx status interrupt enable */ ucon |= S3C44B0X_UCON_RX_ERR_INT_EN; /* Rx timeout enable (5 bits) */ ucon |= S3C44B0X_UCON_RX_TIMEOUT_EN; outb(ucon, S3C44B0X_UCON0 + info->uart_offset); tx_start(info); rx_start(info); return; } info->baud = baud_table[table_index].rate; uart_speed(info, cflag); /* UART line control register setup */ ulcon = 0x00; switch (cflag & CSIZE) { case CS5: ulcon |= S3C44B0X_ULCON_WORDLN_5; break; case CS6: ulcon |= S3C44B0X_ULCON_WORDLN_6; break; case CS7: ulcon |= S3C44B0X_ULCON_WORDLN_7; break; case CS8: ulcon |= S3C44B0X_ULCON_WORDLN_8; break; } if (cflag & PARENB) ulcon |= (cflag & PARODD) ? S3C44B0X_ULCON_PAR_ODD : S3C44B0X_ULCON_PAR_EVEN; if (cflag & CSTOPB) ulcon |= S3C44B0X_ULCON_STOPB_2; outb(ulcon, S3C44B0X_ULCON0 + info->uart_offset); /* UART control register setup */ ucon = 0; /* receive mode: IRQ or polling */ ucon |= S3C44B0X_UCON_RX_MODE_INT_POLL; /* transmit mode: IRQ or polling */ ucon |= S3C44B0X_UCON_TX_MODE_INT_POLL; /* Rx status interrupt enable */ ucon |= S3C44B0X_UCON_RX_ERR_INT_EN; /* Rx timeout enable (5 bits) */ ucon |= S3C44B0X_UCON_RX_TIMEOUT_EN; outb(ucon, S3C44B0X_UCON0 + info->uart_offset); tx_start(info); rx_start(info); return;}/* * Wait for transmitter & holding register to empty */static inline void wait_for_xmitr(struct s3c44b0x_serial *info){ volatile unsigned int status, tmout = 100000; do { status = inb(S3C44B0X_UTRSTAT0 + info->uart_offset); if (--tmout == 0) break; } while((status & (S3C44B0X_UTRSTAT_TBE | \ S3C44B0X_UTRSTAT_TSE)) != \ (S3C44B0X_UTRSTAT_TBE | \ S3C44B0X_UTRSTAT_TSE));}static _INLINE_ void xmit_char(struct s3c44b0x_serial *info, char ch){ wait_for_xmitr(info); tx_delay(); outb(ch, S3C44B0X_UTXH0 + info->uart_offset);}static void rs_flush_chars(struct tty_struct *tty){ struct s3c44b0x_serial *info = (struct s3c44b0x_serial *) tty->driver_data; unsigned long flags; if (serial_paranoia_check(info, tty->device, "rs_flush_chars")) return; if (!info->use_ints) { for (;;) { if (info->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped || !info->xmit_buf) return; save_flags(flags); cli(); tx_start(info); xmit_char(info, info->xmit_buf[info->xmit_tail++]); info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE - 1); info->xmit_cnt--; } } else { if (info->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped || !info->xmit_buf) return; save_flags(flags); cli(); tx_start(info); xmit_char(info, info->xmit_buf[info->xmit_tail++]); info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE - 1); info->xmit_cnt--; } restore_flags(flags);}static int rs_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count){ int c, total = 0; struct s3c44b0x_serial *info = (struct s3c44b0x_serial *) tty->driver_data; unsigned long flags; if (serial_paranoia_check(info, tty->device, "rs_write")) return 0; if (!tty || !info->xmit_buf || !tmp_buf) return 0; save_flags(flags); if (from_user) { down(&tmp_buf_sem); cli(); while (1) { c = MIN(count, MIN(SERIAL_XMIT_SIZE - info->xmit_cnt - 1, SERIAL_XMIT_SIZE - info->xmit_head)); if (c <= 0) break; memcpy(info->xmit_buf + info->xmit_head, buf, c); info->xmit_head = (info->xmit_head + c) & (SERIAL_XMIT_SIZE - 1); info->xmit_cnt += c; restore_flags(flags); buf += c; count -= c; total += c; } restore_flags(flags); up(&tmp_buf_sem); } else {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -