/* * mcfserial.c -- serial driver for ColdFire internal UARTS. * * Copyright (C) 1999-2003 Greg Ungerer <gerg@snapgear.com> * Copyright (c) 2000-2001 Lineo, Inc. <www.lineo.com> * Copyright (C) 2001-2002 SnapGear Inc. <www.snapgear.com> * * Based on code from 68332serial.c which was: * * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) * Copyright (C) 1998 TSHG * Copyright (c) 1999 Rt-Control Inc. <jeff@uclinux.org> * * Changes: * 08/07/2003 Daniele Bellucci <bellucda@tiscali.it> * some cleanups in mcfrs_write. * */ #include <linux/module.h> #include <linux/errno.h> #include <linux/signal.h> #include <linux/sched.h> #include <linux/timer.h> #include <linux/wait.h> #include <linux/interrupt.h> #include <linux/tty.h> #include <linux/tty_flip.h> #include <linux/string.h> #include <linux/fcntl.h> #include <linux/mm.h> #include <linux/kernel.h> #include <linux/serial.h> #include <linux/serialP.h> #include <linux/console.h> #include <linux/init.h> #include <linux/bitops.h> #include <linux/delay.h> #include <asm/io.h> #include <asm/irq.h> #include <asm/system.h> #include <asm/segment.h> #include <asm/semaphore.h> #include <asm/delay.h> #include <asm/coldfire.h> #include <asm/mcfsim.h> #include <asm/mcfuart.h> #include <asm/nettel.h> #include <asm/uaccess.h> #include "mcfserial.h" struct timer_list mcfrs_timer_struct; /* * Default console baud rate, we use this as the default * for all ports so init can just open /dev/console and * keep going. Perhaps one day the cflag settings for the * console can be used instead. */ #if defined(CONFIG_ARNEWSH) || defined(CONFIG_MOTOROLA) || defined(CONFIG_senTec) || defined(CONFIG_SNEHA) #define CONSOLE_BAUD_RATE 19200 #define DEFAULT_CBAUD B19200 #endif #if defined(CONFIG_HW_FEITH) #define CONSOLE_BAUD_RATE 38400 #define DEFAULT_CBAUD B38400 #endif #ifndef CONSOLE_BAUD_RATE #define CONSOLE_BAUD_RATE 9600 #define DEFAULT_CBAUD B9600 #endif int mcfrs_console_inited = 0; int mcfrs_console_port = -1; int mcfrs_console_baud = CONSOLE_BAUD_RATE; int mcfrs_console_cbaud = DEFAULT_CBAUD; /* * Driver data structures. */ static struct tty_driver *mcfrs_serial_driver; /* number of characters left in xmit buffer before we ask for more */ #define WAKEUP_CHARS 256 /* Debugging... */ #undef SERIAL_DEBUG_OPEN #undef SERIAL_DEBUG_FLOW #if defined(CONFIG_M527x) || defined(CONFIG_M528x) #define IRQBASE (MCFINT_VECBASE+MCFINT_UART0) #else #define IRQBASE 73 #endif /* * Configuration table, UARTs to look for at startup. */ static struct mcf_serial mcfrs_table[] = { { /* ttyS0 */ .magic = 0, .addr = (volatile unsigned char *) (MCF_MBAR+MCFUART_BASE1), .irq = IRQBASE, .flags = ASYNC_BOOT_AUTOCONF, }, { /* ttyS1 */ .magic = 0, .addr = (volatile unsigned char *) (MCF_MBAR+MCFUART_BASE2), .irq = IRQBASE+1, .flags = ASYNC_BOOT_AUTOCONF, }, }; #define NR_PORTS (sizeof(mcfrs_table) / sizeof(struct mcf_serial)) /* * This is used to figure out the divisor speeds and the timeouts. */ static int mcfrs_baud_table[] = { 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 0 }; #define MCFRS_BAUD_TABLE_SIZE \ (sizeof(mcfrs_baud_table)/sizeof(mcfrs_baud_table[0])) #ifdef CONFIG_MAGIC_SYSRQ /* * Magic system request keys. Used for debugging... */ extern int magic_sysrq_key(int ch); #endif /* * Forware declarations... */ static void mcfrs_change_speed(struct mcf_serial *info); static void mcfrs_wait_until_sent(struct tty_struct *tty, int timeout); static inline int serial_paranoia_check(struct mcf_serial *info, char *name, const char *routine) { #ifdef SERIAL_PARANOIA_CHECK static const char badmagic[] = "MCFRS(warning): bad magic number for serial struct %s in %s\n"; static const char badinfo[] = "MCFRS(warning): null mcf_serial for %s in %s\n"; if (!info) { printk(badinfo, name, routine); return 1; } if (info->magic != SERIAL_MAGIC) { printk(badmagic, name, routine); return 1; } #endif return 0; } /* * Sets or clears DTR and RTS on the requested line. */ static void mcfrs_setsignals(struct mcf_serial *info, int dtr, int rts) { volatile unsigned char *uartp; unsigned long flags; #if 0 printk("%s(%d): mcfrs_setsignals(info=%x,dtr=%d,rts=%d)\n", __FILE__, __LINE__, info, dtr, rts); #endif local_irq_save(flags); if (dtr >= 0) { #ifdef MCFPP_DTR0 if (info->line) mcf_setppdata(MCFPP_DTR1, (dtr ? 0 : MCFPP_DTR1)); else mcf_setppdata(MCFPP_DTR0, (dtr ? 0 : MCFPP_DTR0)); #endif } if (rts >= 0) { uartp = info->addr; if (rts) { info->sigs |= TIOCM_RTS; uartp[MCFUART_UOP1] = MCFUART_UOP_RTS; } else { info->sigs &= ~TIOCM_RTS; uartp[MCFUART_UOP0] = MCFUART_UOP_RTS; } } local_irq_restore(flags); return; } /* * Gets values of serial signals. */ static int mcfrs_getsignals(struct mcf_serial *info) { volatile unsigned char *uartp; unsigned long flags; int sigs; #if defined(CONFIG_NETtel) && defined(CONFIG_M5307) unsigned short ppdata; #endif #if 0 printk("%s(%d): mcfrs_getsignals(info=%x)\n", __FILE__, __LINE__); #endif local_irq_save(flags); uartp = info->addr; sigs = (uartp[MCFUART_UIPR] & MCFUART_UIPR_CTS) ? 0 : TIOCM_CTS; sigs |= (info->sigs & TIOCM_RTS); #ifdef MCFPP_DCD0 { unsigned int ppdata; ppdata = mcf_getppdata(); if (info->line == 0) { sigs |= (ppdata & MCFPP_DCD0) ? 0 : TIOCM_CD; sigs |= (ppdata & MCFPP_DTR0) ? 0 : TIOCM_DTR; } else if (info->line == 1) { sigs |= (ppdata & MCFPP_DCD1) ? 0 : TIOCM_CD; sigs |= (ppdata & MCFPP_DTR1) ? 0 : TIOCM_DTR; } } #endif local_irq_restore(flags); return(sigs); } /* * ------------------------------------------------------------ * mcfrs_stop() and mcfrs_start() * * This routines are called before setting or resetting tty->stopped. * They enable or disable transmitter interrupts, as necessary. * ------------------------------------------------------------ */ static void mcfrs_stop(struct tty_struct *tty) { volatile unsigned char *uartp; struct mcf_serial *info = (struct mcf_serial *)tty->driver_data; unsigned long flags; if (serial_paranoia_check(info, tty->name, "mcfrs_stop")) return; local_irq_save(flags); uartp = info->addr; info->imr &= ~MCFUART_UIR_TXREADY; uartp[MCFUART_UIMR] = info->imr; local_irq_restore(flags); } static void mcfrs_start(struct tty_struct *tty) { volatile unsigned char *uartp; struct mcf_serial *info = (struct mcf_serial *)tty->driver_data; unsigned long flags; if (serial_paranoia_check(info, tty->name, "mcfrs_start")) return; local_irq_save(flags); if (info->xmit_cnt && info->xmit_buf) { uartp = info->addr; info->imr |= MCFUART_UIR_TXREADY; uartp[MCFUART_UIMR] = info->imr; } local_irq_restore(flags); } /* * ---------------------------------------------------------------------- * * Here starts the interrupt handling routines. All of the following * subroutines are declared as inline and are folded into * mcfrs_interrupt(). They were separated out for readability's sake. * * Note: mcfrs_interrupt() is a "fast" interrupt, which means that it * runs with interrupts turned off. People who may want to modify * mcfrs_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 * ----------------------------------------------------------------------- */ static inline void receive_chars(struct mcf_serial *info) { volatile unsigned char *uartp; struct tty_struct *tty = info->tty; unsigned char status, ch; if (!tty) return; uartp = info->addr; while ((status = uartp[MCFUART_USR]) & MCFUART_USR_RXREADY) { if (tty->flip.count >= TTY_FLIPBUF_SIZE) break; ch = uartp[MCFUART_URB]; info->stats.rx++; #ifdef CONFIG_MAGIC_SYSRQ if (mcfrs_console_inited && (info->line == mcfrs_console_port)) { if (magic_sysrq_key(ch)) continue; } #endif tty->flip.count++; if (status & MCFUART_USR_RXERR) { uartp[MCFUART_UCR] = MCFUART_UCR_CMDRESETERR; if (status & MCFUART_USR_RXBREAK) { info->stats.rxbreak++; *tty->flip.flag_buf_ptr++ = TTY_BREAK; } else if (status & MCFUART_USR_RXPARITY) { info->stats.rxparity++; *tty->flip.flag_buf_ptr++ = TTY_PARITY; } else if (status & MCFUART_USR_RXOVERRUN) { info->stats.rxoverrun++; *tty->flip.flag_buf_ptr++ = TTY_OVERRUN; } else if (status & MCFUART_USR_RXFRAMING) { info->stats.rxframing++; *tty->flip.flag_buf_ptr++ = TTY_FRAME; } else { /* This should never happen... */ *tty->flip.flag_buf_ptr++ = 0; } } else { *tty->flip.flag_buf_ptr++ = 0; } *tty->flip.char_buf_ptr++ = ch; } schedule_work(&tty->flip.work); return; } static inline void transmit_chars(struct mcf_serial *info) { volatile unsigned char *uartp; uartp = info->addr; if (info->x_char) { /* Send special char - probably flow control */ uartp[MCFUART_UTB] = info->x_char; info->x_char = 0; info->stats.tx++; } if ((info->xmit_cnt <= 0) || info->tty->stopped) { info->imr &= ~MCFUART_UIR_TXREADY; uartp[MCFUART_UIMR] = info->imr; return; } while (uartp[MCFUART_USR] & MCFUART_USR_TXREADY) { uartp[MCFUART_UTB] = info->xmit_buf[info->xmit_tail++]; info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1); info->stats.tx++; if (--info->xmit_cnt <= 0) break; } if (info->xmit_cnt < WAKEUP_CHARS) schedule_work(&info->tqueue); return; } /* * This is the serial driver's generic interrupt routine */ irqreturn_t mcfrs_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct mcf_serial *info; unsigned char isr; info = &mcfrs_table[(irq - IRQBASE)]; isr = info->addr[MCFUART_UISR] & info->imr; if (isr & MCFUART_UIR_RXREADY) receive_chars(info); if (isr & MCFUART_UIR_TXREADY) transmit_chars(info); return IRQ_HANDLED; } /* * ------------------------------------------------------------------- * Here ends the serial interrupt routines. * ------------------------------------------------------------------- */ static void mcfrs_offintr(void *private) { struct mcf_serial *info = (struct mcf_serial *) private; struct tty_struct *tty; tty = info->tty; if (!tty) return; tty_wakeup(tty); } /* * Change of state on a DCD line. */ void mcfrs_modem_change(struct mcf_serial *info, int dcd) { if (info->count == 0) return; if (info->flags & ASYNC_CHECK_CD) { if (dcd) wake_up_interruptible(&info->open_wait); else schedule_work(&info->tqueue_hangup); } } #ifdef MCFPP_DCD0 unsigned short mcfrs_ppstatus; /* * This subroutine is called when the RS_TIMER goes off. It is used * to monitor the state of the DCD lines - since they have no edge * sensors and interrupt generators. */ static void mcfrs_timer(void) { unsigned int ppstatus, dcdval, i; ppstatus = mcf_getppdata() & (MCFPP_DCD0 | MCFPP_DCD1); if (ppstatus != mcfrs_ppstatus) { for (i = 0; (i < 2); i++) { dcdval = (i ? MCFPP_DCD1 : MCFPP_DCD0); if ((ppstatus & dcdval) != (mcfrs_ppstatus & dcdval)) { mcfrs_modem_change(&mcfrs_table[i], ((ppstatus & dcdval) ? 0 : 1)); } } } mcfrs_ppstatus = ppstatus; /* Re-arm timer */ mcfrs_timer_struct.expires = jiffies + HZ/25; add_timer(&mcfrs_timer_struct); } #endif /* MCFPP_DCD0 */ /* * 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() -> mcfrs_hangup() * */ static void do_serial_hangup(void *private) { struct mcf_serial *info = (struct mcf_serial *) private; struct tty_struct *tty; tty = info->tty; if (!tty) return; tty_hangup(tty); } static int startup(struct mcf_serial * info) { volatile unsigned char *uartp; unsigned long flags; if (info->flags & ASYNC_INITIALIZED) return 0; if (!info->xmit_buf) { info->xmit_buf = (unsigned char *) __get_free_page(GFP_KERNEL); if (!info->xmit_buf) return -ENOMEM; } local_irq_save(flags); #ifdef SERIAL_DEBUG_OPEN printk("starting up ttyS%d (irq %d)...\n", info->line, info->irq); #endif /* * Reset UART, get it into known state... */ uartp = info->addr; uartp[MCFUART_UCR] = MCFUART_UCR_CMDRESETRX; /* reset RX */ uartp[MCFUART_UCR] = MCFUART_UCR_CMDRESETTX; /* reset TX */ mcfrs_setsignals(info, 1, 1); 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 */ mcfrs_change_speed(info); /* * Lastly enable the UART transmitter and receiver, and * interrupt enables. */ info->imr = MCFUART_UIR_RXREADY; uartp[MCFUART_UCR] = MCFUART_UCR_RXENABLE | MCFUART_UCR_TXENABLE; uartp[MCFUART_UIMR] = info->imr; info->flags |= ASYNC_INITIALIZED; local_irq_restore(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 mcf_serial * info) { volatile unsigned char *uartp; unsigned long flags; if (!(info->flags & ASYNC_INITIALIZED)) return; #ifdef SERIAL_DEBUG_OPEN printk("Shutting down serial port %d (irq %d)....\n", info->line, info->irq); #endif local_irq_save(flags); uartp = info->addr; uartp[MCFUART_UIMR] = 0; /* mask all interrupts */ uartp[MCFUART_UCR] = MCFUART_UCR_CMDRESETRX; /* reset RX */ uartp[MCFUART_UCR] = MCFUART_UCR_CMDRESETTX; /* reset TX */ if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) mcfrs_setsignals(info, 0, 0); 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 &= ~ASYNC_INITIALIZED; local_irq_restore(flags); } /* * This routine is called to set the UART divisor registers to match * the specified baud rate for a serial port. */ static void mcfrs_change_speed(struct mcf_serial *info) { volatile unsigned char *uartp; unsigned int baudclk, cflag; unsigned long flags; unsigned char mr1, mr2; int i; #ifdef CONFIG_M5272 unsigned int fraction; #endif if (!info->tty || !info->tty->termios) return; cflag = info->tty->termios->c_cflag; if (info->addr == 0) return; #if 0 printk("%s(%d): mcfrs_change_speed()\n", __FILE__, __LINE__); #endif i = cflag & CBAUD; if (i & CBAUDEX) { i &= ~CBAUDEX; if (i < 1 || i > 4) info->tty->termios->c_cflag &= ~CBAUDEX; else i += 15; } if (i == 0) { mcfrs_setsignals(info, 0, -1); return; } /* compute the baudrate clock */ #ifdef CONFIG_M5272 /* * For the MCF5272, also compute the baudrate fraction. */ baudclk = (MCF_BUSCLK / mcfrs_baud_table[i]) / 32; fraction = MCF_BUSCLK - (baudclk * 32 * mcfrs_baud_table[i]); fraction *= 16; fraction /= (32 * mcfrs_baud_table[i]); #else baudclk = ((MCF_BUSCLK / mcfrs_baud_table[i]) + 16) / 32; #endif info->baud = mcfrs_baud_table[i]; mr1 = MCFUART_MR1_RXIRQRDY | MCFUART_MR1_RXERRCHAR; mr2 = 0; switch (cflag & CSIZE) { case CS5: mr1 |= MCFUART_MR1_CS5; break; case CS6: mr1 |= MCFUART_MR1_CS6; break; case CS7: mr1 |= MCFUART_MR1_CS7; break; case CS8: default: mr1 |= MCFUART_MR1_CS8; break; } if (cflag & PARENB) { if (cflag & CMSPAR) { if (cflag & PARODD) mr1 |= MCFUART_MR1_PARITYMARK; else mr1 |= MCFUART_MR1_PARITYSPACE; } else { if (cflag & PARODD) mr1 |= MCFUART_MR1_PARITYODD; else mr1 |= MCFUART_MR1_PARITYEVEN; } } else { mr1 |= MCFUART_MR1_PARITYNONE; } if (cflag & CSTOPB) mr2 |= MCFUART_MR2_STOP2; else mr2 |= MCFUART_MR2_STOP1; if (cflag & CRTSCTS) { mr1 |= MCFUART_MR1_RXRTS; mr2 |= MCFUART_MR2_TXCTS; } if (cflag & CLOCAL) info->flags &= ~ASYNC_CHECK_CD; else info->flags |= ASYNC_CHECK_CD; uartp = info->addr; local_irq_save(flags); #if 0 printk("%s(%d): mr1=%x mr2=%x baudclk=%x\n", __FILE__, __LINE__, mr1, mr2, baudclk); #endif /* Note: pg 12-16 of MCF5206e User's Manual states that a software reset should be performed prior to changing UMR1,2, UCSR, UACR, bit 7 */ uartp[MCFUART_UCR] = MCFUART_UCR_CMDRESETRX; /* reset RX */ uartp[MCFUART_UCR] = MCFUART_UCR_CMDRESETTX; /* reset TX */ uartp[MCFUART_UCR] = MCFUART_UCR_CMDRESETMRPTR; /* reset MR pointer */ uartp[MCFUART_UMR] = mr1; uartp[MCFUART_UMR] = mr2; uartp[MCFUART_UBG1] = (baudclk & 0xff00) >> 8; /* set msb byte */ uartp[MCFUART_UBG2] = (baudclk & 0xff); /* set lsb byte */ #ifdef CONFIG_M5272 uartp[MCFUART_UFPD] = (fraction & 0xf); /* set fraction */ #endif uartp[MCFUART_UCSR] = MCFUART_UCSR_RXCLKTIMER | MCFUART_UCSR_TXCLKTIMER; uartp[MCFUART_UCR] = MCFUART_UCR_RXENABLE | MCFUART_UCR_TXENABLE; mcfrs_setsignals(info, 1, -1); local_irq_restore(flags); return; } static void mcfrs_flush_chars(struct tty_struct *tty) { volatile unsigned char *uartp; struct mcf_serial *info = (struct mcf_serial *)tty->driver_data; unsigned long flags; if (serial_paranoia_check(info, tty->name, "mcfrs_flush_chars")) return; uartp = (volatile unsigned char *) info->addr; /* * re-enable receiver interrupt */ local_irq_save(flags); if ((!(info->imr & MCFUART_UIR_RXREADY)) && (info->flags & ASYNC_INITIALIZED) ) { info->imr |= MCFUART_UIR_RXREADY; uartp[MCFUART_UIMR] = info->imr; } local_irq_restore(flags); if (info->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped || !info->xmit_buf) return; /* Enable transmitter */ local_irq_save(flags); info->imr |= MCFUART_UIR_TXREADY; uartp[MCFUART_UIMR] = info->imr; local_irq_restore(flags); } static int mcfrs_write(struct tty_struct * tty, const unsigned char *buf, int count) { volatile unsigned char *uartp; struct mcf_serial *info = (struct mcf_serial *)tty->driver_data; unsigned long flags; int c, total = 0; #if 0 printk("%s(%d): mcfrs_write(tty=%x,buf=%x,count=%d)\n", __FILE__, __LINE__, (int)tty, (int)buf, count); #endif if (serial_paranoia_check(info, tty->name, "mcfrs_write")) return 0; if (!tty || !info->xmit_buf) return 0; local_save_flags(flags); while (1) { local_irq_disable(); c = min(count, (int) min(((int)SERIAL_XMIT_SIZE) - info->xmit_cnt - 1, ((int)SERIAL_XMIT_SIZE) - info->xmit_head)); local_irq_restore(flags); if (c <= 0) break; memcpy(info->xmit_buf + info->xmit_head, buf, c); local_irq_disable(); info->xmit_head = (info->xmit_head + c) & (SERIAL_XMIT_SIZE-1); info->xmit_cnt += c; local_irq_restore(flags); buf += c; count -= c; total += c; } local_irq_disable(); uartp = info->addr; info->imr |= MCFUART_UIR_TXREADY; uartp[MCFUART_UIMR] = info->imr; local_irq_restore(flags); return total; } static int mcfrs_write_room(struct tty_struct *tty) { struct mcf_serial *info = (struct mcf_serial *)tty->driver_data; int ret; if (serial_paranoia_check(info, tty->name, "mcfrs_write_room")) return 0; ret = SERIAL_XMIT_SIZE - info->xmit_cnt - 1; if (ret < 0) ret = 0; return ret; } static int mcfrs_chars_in_buffer(struct tty_struct *tty) { struct mcf_serial *info = (struct mcf_serial *)tty->driver_data; if (serial_paranoia_check(info, tty->name, "mcfrs_chars_in_buffer")) return 0; return info->xmit_cnt; } static void mcfrs_flush_buffer(struct tty_struct *tty) { struct mcf_serial *info = (struct mcf_serial *)tty->driver_data; unsigned long flags; if (serial_paranoia_check(info, tty->name, "mcfrs_flush_buffer")) return; local_irq_save(flags); info->xmit_cnt = info->xmit_head = info->xmit_tail = 0; local_irq_restore(flags); tty_wakeup(tty); } /* * ------------------------------------------------------------ * mcfrs_throttle() * * This routine is called by the upper-layer tty layer to signal that * incoming characters should be throttled. * ------------------------------------------------------------ */ static void mcfrs_throttle(struct tty_struct * tty) { struct mcf_serial *info = (struct mcf_serial *)tty->driver_data; #ifdef SERIAL_DEBUG_THROTTLE char buf[64]; printk("throttle %s: %d....\n", _tty_name(tty, buf), tty->ldisc.chars_in_buffer(tty)); #endif if (serial_paranoia_check(info, tty->name, "mcfrs_throttle")) return; if (I_IXOFF(tty)) info->x_char = STOP_CHAR(tty); /* Turn off RTS line (do this atomic) */ } static void mcfrs_unthrottle(struct tty_struct * tty) { struct mcf_serial *info = (struct mcf_serial *)tty->driver_data; #ifdef SERIAL_DEBUG_THROTTLE char buf[64]; printk("unthrottle %s: %d....\n", _tty_name(tty, buf), tty->ldisc.chars_in_buffer(tty)); #endif if (serial_paranoia_check(info, tty->name, "mcfrs_unthrottle")) return; if (I_IXOFF(tty)) { if (info->x_char) info->x_char = 0; else info->x_char = START_CHAR(tty); } /* Assert RTS line (do this atomic) */ } /* * ------------------------------------------------------------ * mcfrs_ioctl() and friends * ------------------------------------------------------------ */ static int get_serial_info(struct mcf_serial * info, struct serial_struct * retinfo) { struct serial_struct tmp; if (!retinfo) return -EFAULT; memset(&tmp, 0, sizeof(tmp)); tmp.type = info->type; tmp.line = info->line; tmp.port = (unsigned int) info->addr; tmp.irq = info->irq; tmp.flags = info->flags; tmp.baud_base = info->baud_base; tmp.close_delay = info->close_delay; tmp.closing_wait = info->closing_wait; tmp.custom_divisor = info->custom_divisor; return copy_to_user(retinfo,&tmp,sizeof(*retinfo)) ? -EFAULT : 0; } static int set_serial_info(struct mcf_serial * info, struct serial_struct * new_info) { struct serial_struct new_serial; struct mcf_serial old_info; int retval = 0; if (!new_info) return -EFAULT; if (copy_from_user(&new_serial,new_info,sizeof(new_serial))) return -EFAULT; old_info = *info; if (!capable(CAP_SYS_ADMIN)) { if ((new_serial.baud_base != info->baud_base) || (new_serial.type != info->type) || (new_serial.close_delay != info->close_delay) || ((new_serial.flags & ~ASYNC_USR_MASK) != (info->flags & ~ASYNC_USR_MASK))) return -EPERM; info->flags = ((info->flags & ~ASYNC_USR_MASK) | (new_serial.flags & ASYNC_USR_MASK)); info->custom_divisor = new_serial.custom_divisor; goto check_and_exit; } if (info->count > 1) return -EBUSY; /* * OK, past this point, all the error checking has been done. * At this point, we start making changes..... */ info->baud_base = new_serial.baud_base; info->flags = ((info->flags & ~ASYNC_FLAGS) | (new_serial.flags & ASYNC_FLAGS)); info->type = new_serial.type; info->close_delay = new_serial.close_delay; info->closing_wait = new_serial.closing_wait; check_and_exit: retval = startup(info); return retval; } /* * get_lsr_info - get line status register info * * Purpose: Let user call ioctl() to get info when the UART physically * is emptied. On bus types like RS485, the transmitter must * release the bus after transmitting. This must be done when * the transmit shift register is empty, not be done when the * transmit holding register is empty. This functionality * allows an RS485 driver to be written in user space. */ static int get_lsr_info(struct mcf_serial * info, unsigned int *value) { volatile unsigned char *uartp; unsigned long flags; unsigned char status; local_irq_save(flags); uartp = info->addr; status = (uartp[MCFUART_USR] & MCFUART_USR_TXEMPTY) ? TIOCSER_TEMT : 0; local_irq_restore(flags); return put_user(status,value); } /* * This routine sends a break character out the serial port. */ static void send_break( struct mcf_serial * info, int duration) { volatile unsigned char *uartp; unsigned long flags; if (!info->addr) return; set_current_state(TASK_INTERRUPTIBLE); uartp = info->addr; local_irq_save(flags); uartp[MCFUART_UCR] = MCFUART_UCR_CMDBREAKSTART; schedule_timeout(duration); uartp[MCFUART_UCR] = MCFUART_UCR_CMDBREAKSTOP; local_irq_restore(flags); } static int mcfrs_tiocmget(struct tty_struct *tty, struct file *file) { struct mcf_serial * info = (struct mcf_serial *)tty->driver_data; if (serial_paranoia_check(info, tty->name, "mcfrs_ioctl")) return -ENODEV; if (tty->flags & (1 << TTY_IO_ERROR)) return -EIO; return mcfrs_getsignals(info); } static int mcfrs_tiocmset(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear) { struct mcf_serial * info = (struct mcf_serial *)tty->driver_data; int rts = -1, dtr = -1; if (serial_paranoia_check(info, tty->name, "mcfrs_ioctl")) return -ENODEV; if (tty->flags & (1 << TTY_IO_ERROR)) return -EIO; if (set & TIOCM_RTS) rts = 1; if (set & TIOCM_DTR) dtr = 1; if (clear & TIOCM_RTS) rts = 0; if (clear & TIOCM_DTR) dtr = 0; mcfrs_setsignals(info, dtr, rts); return 0; } static int mcfrs_ioctl(struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg) { struct mcf_serial * info = (struct mcf_serial *)tty->driver_data; int retval, error; if (serial_paranoia_check(info, tty->name, "mcfrs_ioctl")) return -ENODEV; if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) && (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGWILD) && (cmd != TIOCSERSWILD) && (cmd != TIOCSERGSTRUCT)) { if (tty->flags & (1 << TTY_IO_ERROR)) return -EIO; } switch (cmd) { case TCSBRK: /* SVID version: non-zero arg --> no break */ retval = tty_check_change(tty); if (retval) return retval; tty_wait_until_sent(tty, 0); if (!arg) send_break(info, HZ/4); /* 1/4 second */ return 0; case TCSBRKP: /* support for POSIX tcsendbreak() */ retval = tty_check_change(tty); if (retval) return retval; tty_wait_until_sent(tty, 0); send_break(info, arg ? arg*(HZ/10) : HZ/4); return 0; case TIOCGSOFTCAR: error = put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long *) arg); if (error) return error; return 0; case TIOCSSOFTCAR: get_user(arg, (unsigned long *) arg); tty->termios->c_cflag = ((tty->termios->c_cflag & ~CLOCAL) | (arg ? CLOCAL : 0)); return 0; case TIOCGSERIAL: if (access_ok(VERIFY_WRITE, (void *) arg, sizeof(struct serial_struct))) return get_serial_info(info, (struct serial_struct *) arg); return -EFAULT; case TIOCSSERIAL: return set_serial_info(info, (struct serial_struct *) arg); case TIOCSERGETLSR: /* Get line status register */ if (access_ok(VERIFY_WRITE, (void *) arg, sizeof(unsigned int))) return get_lsr_info(info, (unsigned int *) arg); return -EFAULT; case TIOCSERGSTRUCT: error = copy_to_user((struct mcf_serial *) arg, info, sizeof(struct mcf_serial)); if (error) return -EFAULT; return 0; #ifdef TIOCSET422 case TIOCSET422: { unsigned int val; get_user(val, (unsigned int *) arg); mcf_setpa(MCFPP_PA11, (val ? 0 : MCFPP_PA11)); break; } case TIOCGET422: { unsigned int val; val = (mcf_getpa() & MCFPP_PA11) ? 0 : 1; put_user(val, (unsigned int *) arg); break; } #endif default: return -ENOIOCTLCMD; } return 0; } static void mcfrs_set_termios(struct tty_struct *tty, struct termios *old_termios) { struct mcf_serial *info = (struct mcf_serial *)tty->driver_data; if (tty->termios->c_cflag == old_termios->c_cflag) return; mcfrs_change_speed(info); if ((old_termios->c_cflag & CRTSCTS) && !(tty->termios->c_cflag & CRTSCTS)) { tty->hw_stopped = 0; mcfrs_setsignals(info, -1, 1); #if 0 mcfrs_start(tty); #endif } } /* * ------------------------------------------------------------ * mcfrs_close() * * This routine is called when the serial port gets closed. First, we * wait for the last remaining data to be sent. Then, we unlink its * S structure from the interrupt chain if necessary, and we free * that IRQ if nothing is left in the chain. * ------------------------------------------------------------ */ static void mcfrs_close(struct tty_struct *tty, struct file * filp) { volatile unsigned char *uartp; struct mcf_serial *info = (struct mcf_serial *)tty->driver_data; unsigned long flags; if (!info || serial_paranoia_check(info, tty->name, "mcfrs_close")) return; local_irq_save(flags); if (tty_hung_up_p(filp)) { local_irq_restore(flags); return; } #ifdef SERIAL_DEBUG_OPEN printk("mcfrs_close ttyS%d, count = %d\n", info->line, info->count); #endif if ((tty->count == 1) && (info->count != 1)) { /* * Uh, oh. tty->count is 1, which means that the tty * structure will be freed. Info->count should always * be one in these conditions. If it's greater than * one, we've got real problems, since it means the * serial port won't be shutdown. */ printk("MCFRS: bad serial port count; tty->count is 1, " "info->count is %d\n", info->count); info->count = 1; } if (--info->count < 0) { printk("MCFRS: bad serial port count for ttyS%d: %d\n", info->line, info->count); info->count = 0; } if (info->count) { local_irq_restore(flags); return; } info->flags |= ASYNC_CLOSING; /* * Now we wait for the transmit buffer to clear; and we notify * the line discipline to only process XON/XOFF characters. */ tty->closing = 1; if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE) tty_wait_until_sent(tty, info->closing_wait); /* * At this point we stop accepting input. To do this, we * disable the receive line status interrupts, and tell the * interrupt driver to stop checking the data ready bit in the * line status register. */ info->imr &= ~MCFUART_UIR_RXREADY; uartp = info->addr; uartp[MCFUART_UIMR] = info->imr; #if 0 /* FIXME: do we need to keep this enabled for console?? */ if (mcfrs_console_inited && (mcfrs_console_port == info->line)) { /* Do not disable the UART */ ; } else #endif shutdown(info); if (tty->driver->flush_buffer) tty->driver->flush_buffer(tty); tty_ldisc_flush(tty); tty->closing = 0; info->event = 0; info->tty = 0; #if 0 if (tty->ldisc.num != ldiscs[N_TTY].num) { if (tty->ldisc.close) (tty->ldisc.close)(tty); tty->ldisc = ldiscs[N_TTY]; tty->termios->c_line = N_TTY; if (tty->ldisc.open) (tty->ldisc.open)(tty); } #endif if (info->blocked_open) { if (info->close_delay) { msleep_interruptible(jiffies_to_msecs(info->close_delay)); } wake_up_interruptible(&info->open_wait); } info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING); wake_up_interruptible(&info->close_wait); local_irq_restore(flags); } /* * mcfrs_wait_until_sent() --- wait until the transmitter is empty */ static void mcfrs_wait_until_sent(struct tty_struct *tty, int timeout) { #ifdef CONFIG_M5272 #define MCF5272_FIFO_SIZE 25 /* fifo size + shift reg */ struct mcf_serial * info = (struct mcf_serial *)tty->driver_data; volatile unsigned char *uartp; unsigned long orig_jiffies, fifo_time, char_time, fifo_cnt; if (serial_paranoia_check(info, tty->name, "mcfrs_wait_until_sent")) return; orig_jiffies = jiffies; /* * Set the check interval to be 1/5 of the approximate time * to send the entire fifo, and make it at least 1. The check * interval should also be less than the timeout. * * Note: we have to use pretty tight timings here to satisfy * the NIST-PCTS. */ fifo_time = (MCF5272_FIFO_SIZE * HZ * 10) / info->baud; char_time = fifo_time / 5; if (char_time == 0) char_time = 1; if (timeout && timeout < char_time) char_time = timeout; /* * Clamp the timeout period at 2 * the time to empty the * fifo. Just to be safe, set the minimum at .5 seconds. */ fifo_time *= 2; if (fifo_time < (HZ/2)) fifo_time = HZ/2; if (!timeout || timeout > fifo_time) timeout = fifo_time; /* * Account for the number of bytes in the UART * transmitter FIFO plus any byte being shifted out. */ uartp = (volatile unsigned char *) info->addr; for (;;) { fifo_cnt = (uartp[MCFUART_UTF] & MCFUART_UTF_TXB); if ((uartp[MCFUART_USR] & (MCFUART_USR_TXREADY| MCFUART_USR_TXEMPTY)) == MCFUART_USR_TXREADY) fifo_cnt++; if (fifo_cnt == 0) break; msleep_interruptible(jiffies_to_msecs(char_time)); if (signal_pending(current)) break; if (timeout && time_after(jiffies, orig_jiffies + timeout)) break; } #else /* * For the other coldfire models, assume all data has been sent */ #endif } /* * mcfrs_hangup() --- called by tty_hangup() when a hangup is signaled. */ void mcfrs_hangup(struct tty_struct *tty) { struct mcf_serial * info = (struct mcf_serial *)tty->driver_data; if (serial_paranoia_check(info, tty->name, "mcfrs_hangup")) return; mcfrs_flush_buffer(tty); shutdown(info); info->event = 0; info->count = 0; info->flags &= ~ASYNC_NORMAL_ACTIVE; info->tty = 0; wake_up_interruptible(&info->open_wait); } /* * ------------------------------------------------------------ * mcfrs_open() and friends * ------------------------------------------------------------ */ static int block_til_ready(struct tty_struct *tty, struct file * filp, struct mcf_serial *info) { DECLARE_WAITQUEUE(wait, current); int retval; int do_clocal = 0; /* * If the device is in the middle of being closed, then block * until it's done, and then try again. */ if (info->flags & ASYNC_CLOSING) { interruptible_sleep_on(&info->close_wait); #ifdef SERIAL_DO_RESTART if (info->flags & ASYNC_HUP_NOTIFY) return -EAGAIN; else return -ERESTARTSYS; #else return -EAGAIN; #endif } /* * If non-blocking mode is set, or the port is not enabled, * then make the check up front and then exit. */ if ((filp->f_flags & O_NONBLOCK) || (tty->flags & (1 << TTY_IO_ERROR))) { info->flags |= ASYNC_NORMAL_ACTIVE; return 0; } if (tty->termios->c_cflag & CLOCAL) do_clocal = 1; /* * Block waiting for the carrier detect and the line to become * free (i.e., not in use by the callout). While we are in * this loop, info->count is dropped by one, so that * mcfrs_close() knows when to free things. We restore it upon * exit, either normal or abnormal. */ retval = 0; add_wait_queue(&info->open_wait, &wait); #ifdef SERIAL_DEBUG_OPEN printk("block_til_ready before block: ttyS%d, count = %d\n", info->line, info->count); #endif info->count--; info->blocked_open++; while (1) { local_irq_disable(); mcfrs_setsignals(info, 1, 1); local_irq_enable(); current->state = TASK_INTERRUPTIBLE; if (tty_hung_up_p(filp) || !(info->flags & ASYNC_INITIALIZED)) { #ifdef SERIAL_DO_RESTART if (info->flags & ASYNC_HUP_NOTIFY) retval = -EAGAIN; else retval = -ERESTARTSYS; #else retval = -EAGAIN; #endif break; } if (!(info->flags & ASYNC_CLOSING) && (do_clocal || (mcfrs_getsignals(info) & TIOCM_CD))) break; if (signal_pending(current)) { retval = -ERESTARTSYS; break; } #ifdef SERIAL_DEBUG_OPEN printk("block_til_ready blocking: ttyS%d, count = %d\n", info->line, info->count); #endif schedule(); } current->state = TASK_RUNNING; remove_wait_queue(&info->open_wait, &wait); if (!tty_hung_up_p(filp)) info->count++; info->blocked_open--; #ifdef SERIAL_DEBUG_OPEN printk("block_til_ready after blocking: ttyS%d, count = %d\n", info->line, info->count); #endif if (retval) return retval; info->flags |= ASYNC_NORMAL_ACTIVE; return 0; } /* * This routine is called whenever a serial port is opened. It * enables interrupts for a serial port, linking in its structure into * the IRQ chain. It also performs the serial-specific * initialization for the tty structure. */ int mcfrs_open(struct tty_struct *tty, struct file * filp) { struct mcf_serial *info; int retval, line; line = tty->index; if ((line < 0) || (line >= NR_PORTS)) return -ENODEV; info = mcfrs_table + line; if (serial_paranoia_check(info, tty->name, "mcfrs_open")) return -ENODEV; #ifdef SERIAL_DEBUG_OPEN printk("mcfrs_open %s, count = %d\n", tty->name, info->count); #endif info->count++; tty->driver_data = info; info->tty = tty; /* * Start up serial port */ retval = startup(info); if (retval) return retval; retval = block_til_ready(tty, filp, info); if (retval) { #ifdef SERIAL_DEBUG_OPEN printk("mcfrs_open returning after block_til_ready with %d\n", retval); #endif return retval; } #ifdef SERIAL_DEBUG_OPEN printk("mcfrs_open %s successful...\n", tty->name); #endif return 0; } /* * Based on the line number set up the internal interrupt stuff. */ static void mcfrs_irqinit(struct mcf_serial *info) { #if defined(CONFIG_M5272) volatile unsigned long *icrp; volatile unsigned long *portp; volatile unsigned char *uartp; uartp = info->addr; icrp = (volatile unsigned long *) (MCF_MBAR + MCFSIM_ICR2); switch (info->line) { case 0: *icrp = 0xe0000000; break; case 1: *icrp = 0x0e000000; break; default: printk("MCFRS: don't know how to handle UART %d interrupt?\n", info->line); return; } /* Enable the output lines for the serial ports */ portp = (volatile unsigned long *) (MCF_MBAR + MCFSIM_PBCNT); *portp = (*portp & ~0x000000ff) | 0x00000055; portp = (volatile unsigned long *) (MCF_MBAR + MCFSIM_PDCNT); *portp = (*portp & ~0x000003fc) | 0x000002a8; #elif defined(CONFIG_M527x) || defined(CONFIG_M528x) volatile unsigned char *icrp, *uartp; volatile unsigned long *imrp; uartp = info->addr; icrp = (volatile unsigned char *) (MCF_MBAR + MCFICM_INTC0 + MCFINTC_ICR0 + MCFINT_UART0 + info->line); *icrp = 0x33; /* UART0 with level 6, priority 3 */ imrp = (volatile unsigned long *) (MCF_MBAR + MCFICM_INTC0 + MCFINTC_IMRL); *imrp &= ~((1 << (info->irq - MCFINT_VECBASE)) | 1); #else volatile unsigned char *icrp, *uartp; switch (info->line) { case 0: icrp = (volatile unsigned char *) (MCF_MBAR + MCFSIM_UART1ICR); *icrp = /*MCFSIM_ICR_AUTOVEC |*/ MCFSIM_ICR_LEVEL6 | MCFSIM_ICR_PRI1; mcf_setimr(mcf_getimr() & ~MCFSIM_IMR_UART1); break; case 1: icrp = (volatile unsigned char *) (MCF_MBAR + MCFSIM_UART2ICR); *icrp = /*MCFSIM_ICR_AUTOVEC |*/ MCFSIM_ICR_LEVEL6 | MCFSIM_ICR_PRI2; mcf_setimr(mcf_getimr() & ~MCFSIM_IMR_UART2); break; default: printk("MCFRS: don't know how to handle UART %d interrupt?\n", info->line); return; } uartp = info->addr; uartp[MCFUART_UIVR] = info->irq; #endif /* Clear mask, so no surprise interrupts. */ uartp[MCFUART_UIMR] = 0; if (request_irq(info->irq, mcfrs_interrupt, SA_INTERRUPT, "ColdFire UART", NULL)) { printk("MCFRS: Unable to attach ColdFire UART %d interrupt " "vector=%d\n", info->line, info->irq); } return; } char *mcfrs_drivername = "ColdFire internal UART serial driver version 1.00\n"; /* * Serial stats reporting... */ int mcfrs_readproc(char *page, char **start, off_t off, int count, int *eof, void *data) { struct mcf_serial *info; char str[20]; int len, sigs, i; len = sprintf(page, mcfrs_drivername); for (i = 0; (i < NR_PORTS); i++) { info = &mcfrs_table[i]; len += sprintf((page + len), "%d: port:%x irq=%d baud:%d ", i, (unsigned int) info->addr, info->irq, info->baud); if (info->stats.rx || info->stats.tx) len += sprintf((page + len), "tx:%d rx:%d ", info->stats.tx, info->stats.rx); if (info->stats.rxframing) len += sprintf((page + len), "fe:%d ", info->stats.rxframing); if (info->stats.rxparity) len += sprintf((page + len), "pe:%d ", info->stats.rxparity); if (info->stats.rxbreak) len += sprintf((page + len), "brk:%d ", info->stats.rxbreak); if (info->stats.rxoverrun) len += sprintf((page + len), "oe:%d ", info->stats.rxoverrun); str[0] = str[1] = 0; if ((sigs = mcfrs_getsignals(info))) { if (sigs & TIOCM_RTS) strcat(str, "|RTS"); if (sigs & TIOCM_CTS) strcat(str, "|CTS"); if (sigs & TIOCM_DTR) strcat(str, "|DTR"); if (sigs & TIOCM_CD) strcat(str, "|CD"); } len += sprintf((page + len), "%s\n", &str[1]); } return(len); } /* Finally, routines used to initialize the serial driver. */ static void show_serial_version(void) { printk(mcfrs_drivername); } static struct tty_operations mcfrs_ops = { .open = mcfrs_open, .close = mcfrs_close, .write = mcfrs_write, .flush_chars = mcfrs_flush_chars, .write_room = mcfrs_write_room, .chars_in_buffer = mcfrs_chars_in_buffer, .flush_buffer = mcfrs_flush_buffer, .ioctl = mcfrs_ioctl, .throttle = mcfrs_throttle, .unthrottle = mcfrs_unthrottle, .set_termios = mcfrs_set_termios, .stop = mcfrs_stop, .start = mcfrs_start, .hangup = mcfrs_hangup, .read_proc = mcfrs_readproc, .wait_until_sent = mcfrs_wait_until_sent, .tiocmget = mcfrs_tiocmget, .tiocmset = mcfrs_tiocmset, }; /* mcfrs_init inits the driver */ static int __init mcfrs_init(void) { struct mcf_serial *info; unsigned long flags; int i; /* Setup base handler, and timer table. */ #ifdef MCFPP_DCD0 init_timer(&mcfrs_timer_struct); mcfrs_timer_struct.function = mcfrs_timer; mcfrs_timer_struct.data = 0; mcfrs_timer_struct.expires = jiffies + HZ/25; add_timer(&mcfrs_timer_struct); mcfrs_ppstatus = mcf_getppdata() & (MCFPP_DCD0 | MCFPP_DCD1); #endif mcfrs_serial_driver = alloc_tty_driver(NR_PORTS); if (!mcfrs_serial_driver) return -ENOMEM; show_serial_version(); /* Initialize the tty_driver structure */ mcfrs_serial_driver->owner = THIS_MODULE; mcfrs_serial_driver->name = "ttyS"; mcfrs_serial_driver->devfs_name = "ttys/"; mcfrs_serial_driver->driver_name = "serial"; mcfrs_serial_driver->major = TTY_MAJOR; mcfrs_serial_driver->minor_start = 64; mcfrs_serial_driver->type = TTY_DRIVER_TYPE_SERIAL; mcfrs_serial_driver->subtype = SERIAL_TYPE_NORMAL; mcfrs_serial_driver->init_termios = tty_std_termios; mcfrs_serial_driver->init_termios.c_cflag = mcfrs_console_cbaud | CS8 | CREAD | HUPCL | CLOCAL; mcfrs_serial_driver->flags = TTY_DRIVER_REAL_RAW; tty_set_operations(mcfrs_serial_driver, &mcfrs_ops); if (tty_register_driver(mcfrs_serial_driver)) { printk("MCFRS: Couldn't register serial driver\n"); put_tty_driver(mcfrs_serial_driver); return(-EBUSY); } local_irq_save(flags); /* * Configure all the attached serial ports. */ for (i = 0, info = mcfrs_table; (i < NR_PORTS); i++, info++) { info->magic = SERIAL_MAGIC; info->line = i; info->tty = 0; info->custom_divisor = 16; info->close_delay = 50; info->closing_wait = 3000; info->x_char = 0; info->event = 0; info->count = 0; info->blocked_open = 0; INIT_WORK(&info->tqueue, mcfrs_offintr, info); INIT_WORK(&info->tqueue_hangup, do_serial_hangup, info); init_waitqueue_head(&info->open_wait); init_waitqueue_head(&info->close_wait); info->imr = 0; mcfrs_setsignals(info, 0, 0); mcfrs_irqinit(info); printk("ttyS%d at 0x%04x (irq = %d)", info->line, (unsigned int) info->addr, info->irq); printk(" is a builtin ColdFire UART\n"); } local_irq_restore(flags); return 0; } module_init(mcfrs_init); /****************************************************************************/ /* Serial Console */ /****************************************************************************/ /* * Quick and dirty UART initialization, for console output. */ void mcfrs_init_console(void) { volatile unsigned char *uartp; unsigned int clk; /* * Reset UART, get it into known state... */ uartp = (volatile unsigned char *) (MCF_MBAR + (mcfrs_console_port ? MCFUART_BASE2 : MCFUART_BASE1)); uartp[MCFUART_UCR] = MCFUART_UCR_CMDRESETRX; /* reset RX */ uartp[MCFUART_UCR] = MCFUART_UCR_CMDRESETTX; /* reset TX */ uartp[MCFUART_UCR] = MCFUART_UCR_CMDRESETMRPTR; /* reset MR pointer */ /* * Set port for defined baud , 8 data bits, 1 stop bit, no parity. */ uartp[MCFUART_UMR] = MCFUART_MR1_PARITYNONE | MCFUART_MR1_CS8; uartp[MCFUART_UMR] = MCFUART_MR2_STOP1; clk = ((MCF_BUSCLK / mcfrs_console_baud) + 16) / 32; /* set baud */ uartp[MCFUART_UBG1] = (clk & 0xff00) >> 8; /* set msb baud */ uartp[MCFUART_UBG2] = (clk & 0xff); /* set lsb baud */ uartp[MCFUART_UCSR] = MCFUART_UCSR_RXCLKTIMER | MCFUART_UCSR_TXCLKTIMER; uartp[MCFUART_UCR] = MCFUART_UCR_RXENABLE | MCFUART_UCR_TXENABLE; mcfrs_console_inited++; return; } /* * Setup for console. Argument comes from the boot command line. */ int mcfrs_console_setup(struct console *cp, char *arg) { int i, n = CONSOLE_BAUD_RATE; if (!cp) return(-1); if (!strncmp(cp->name, "ttyS", 4)) mcfrs_console_port = cp->index; else if (!strncmp(cp->name, "cua", 3)) mcfrs_console_port = cp->index; else return(-1); if (arg) n = simple_strtoul(arg,NULL,0); for (i = 0; i < MCFRS_BAUD_TABLE_SIZE; i++) if (mcfrs_baud_table[i] == n) break; if (i < MCFRS_BAUD_TABLE_SIZE) { mcfrs_console_baud = n; mcfrs_console_cbaud = 0; if (i > 15) { mcfrs_console_cbaud |= CBAUDEX; i -= 15; } mcfrs_console_cbaud |= i; } mcfrs_init_console(); /* make sure baud rate changes */ return(0); } static struct tty_driver *mcfrs_console_device(struct console *c, int *index) { *index = c->index; return mcfrs_serial_driver; } /* * Output a single character, using UART polled mode. * This is used for console output. */ void mcfrs_put_char(char ch) { volatile unsigned char *uartp; unsigned long flags; int i; uartp = (volatile unsigned char *) (MCF_MBAR + (mcfrs_console_port ? MCFUART_BASE2 : MCFUART_BASE1)); local_irq_save(flags); for (i = 0; (i < 0x10000); i++) { if (uartp[MCFUART_USR] & MCFUART_USR_TXREADY) break; } if (i < 0x10000) { uartp[MCFUART_UTB] = ch; for (i = 0; (i < 0x10000); i++) if (uartp[MCFUART_USR] & MCFUART_USR_TXEMPTY) break; } if (i >= 0x10000) mcfrs_init_console(); /* try and get it back */ local_irq_restore(flags); return; } /* * rs_console_write is registered for printk output. */ void mcfrs_console_write(struct console *cp, const char *p, unsigned len) { if (!mcfrs_console_inited) mcfrs_init_console(); while (len-- > 0) { if (*p == '\n') mcfrs_put_char('\r'); mcfrs_put_char(*p++); } } /* * declare our consoles */ struct console mcfrs_console = { .name = "ttyS", .write = mcfrs_console_write, .device = mcfrs_console_device, .setup = mcfrs_console_setup, .flags = CON_PRINTBUFFER, .index = -1, }; static int __init mcfrs_console_init(void) { register_console(&mcfrs_console); return 0; } console_initcall(mcfrs_console_init); /****************************************************************************/