/* * linux/drivers/char/serial_21285.c * * Driver for the serial port on the 21285 StrongArm-110 core logic chip. * * Based on drivers/char/serial.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BAUD_BASE (mem_fclk_21285/64) #define SERIAL_21285_NAME "ttyFB" #define SERIAL_21285_MAJOR 204 #define SERIAL_21285_MINOR 4 #define SERIAL_21285_AUXNAME "cuafb" #define SERIAL_21285_AUXMAJOR 205 #define SERIAL_21285_AUXMINOR 4 static struct tty_driver rs285_driver, callout_driver; static int rs285_refcount; static struct tty_struct *rs285_table[1]; static struct termios *rs285_termios[1]; static struct termios *rs285_termios_locked[1]; static char wbuf[1000], *putp = wbuf, *getp = wbuf, x_char; static struct tty_struct *rs285_tty; static int rs285_use_count; static int rs285_write_room(struct tty_struct *tty) { return putp >= getp ? (sizeof(wbuf) - (long) putp + (long) getp) : ((long) getp - (long) putp - 1); } static void rs285_rx_int(int irq, void *dev_id, struct pt_regs *regs) { if (!rs285_tty) { disable_irq(IRQ_CONRX); return; } while (!(*CSR_UARTFLG & 0x10)) { int ch, flag; ch = *CSR_UARTDR; flag = *CSR_RXSTAT; if (flag & 4) tty_insert_flip_char(rs285_tty, 0, TTY_OVERRUN); if (flag & 2) flag = TTY_PARITY; else if (flag & 1) flag = TTY_FRAME; tty_insert_flip_char(rs285_tty, ch, flag); } tty_flip_buffer_push(rs285_tty); } static void rs285_send_xchar(struct tty_struct *tty, char ch) { x_char = ch; enable_irq(IRQ_CONTX); } static void rs285_throttle(struct tty_struct *tty) { if (I_IXOFF(tty)) rs285_send_xchar(tty, STOP_CHAR(tty)); } static void rs285_unthrottle(struct tty_struct *tty) { if (I_IXOFF(tty)) { if (x_char) x_char = 0; else rs285_send_xchar(tty, START_CHAR(tty)); } } static void rs285_tx_int(int irq, void *dev_id, struct pt_regs *regs) { while (!(*CSR_UARTFLG & 0x20)) { if (x_char) { *CSR_UARTDR = x_char; x_char = 0; continue; } if (putp == getp) { disable_irq(IRQ_CONTX); break; } *CSR_UARTDR = *getp; if (++getp >= wbuf + sizeof(wbuf)) getp = wbuf; } if (rs285_tty) wake_up_interruptible(&rs285_tty->write_wait); } static inline int rs285_xmit(int ch) { if (putp + 1 == getp || (putp + 1 == wbuf + sizeof(wbuf) && getp == wbuf)) return 0; *putp = ch; if (++putp >= wbuf + sizeof(wbuf)) putp = wbuf; enable_irq(IRQ_CONTX); return 1; } static int rs285_write(struct tty_struct *tty, int from_user, const u_char * buf, int count) { int i; if (from_user && verify_area(VERIFY_READ, buf, count)) return -EINVAL; for (i = 0; i < count; i++) { char ch; if (from_user) __get_user(ch, buf + i); else ch = buf[i]; if (!rs285_xmit(ch)) break; } return i; } static void rs285_put_char(struct tty_struct *tty, u_char ch) { rs285_xmit(ch); } static int rs285_chars_in_buffer(struct tty_struct *tty) { return sizeof(wbuf) - rs285_write_room(tty); } static void rs285_flush_buffer(struct tty_struct *tty) { disable_irq(IRQ_CONTX); putp = getp = wbuf; if (x_char) enable_irq(IRQ_CONTX); } static inline void rs285_set_cflag(int cflag) { int h_lcr, baud, quot; switch (cflag & CSIZE) { case CS5: h_lcr = 0x10; break; case CS6: h_lcr = 0x30; break; case CS7: h_lcr = 0x50; break; default: /* CS8 */ h_lcr = 0x70; break; } if (cflag & CSTOPB) h_lcr |= 0x08; if (cflag & PARENB) h_lcr |= 0x02; if (!(cflag & PARODD)) h_lcr |= 0x04; switch (cflag & CBAUD) { case B200: baud = 200; break; case B300: baud = 300; break; case B1200: baud = 1200; break; case B1800: baud = 1800; break; case B2400: baud = 2400; break; case B4800: baud = 4800; break; default: case B9600: baud = 9600; break; case B19200: baud = 19200; break; case B38400: baud = 38400; break; case B57600: baud = 57600; break; case B115200: baud = 115200; break; } /* * The documented expression for selecting the divisor is: * BAUD_BASE / baud - 1 * However, typically BAUD_BASE is not divisible by baud, so * we want to select the divisor that gives us the minimum * error. Therefore, we want: * int(BAUD_BASE / baud - 0.5) -> * int(BAUD_BASE / baud - (baud >> 1) / baud) -> * int((BAUD_BASE - (baud >> 1)) / baud) */ quot = (BAUD_BASE - (baud >> 1)) / baud; *CSR_UARTCON = 0; *CSR_L_UBRLCR = quot & 0xff; *CSR_M_UBRLCR = (quot >> 8) & 0x0f; *CSR_H_UBRLCR = h_lcr; *CSR_UARTCON = 1; } static void rs285_set_termios(struct tty_struct *tty, struct termios *old) { if (old && tty->termios->c_cflag == old->c_cflag) return; rs285_set_cflag(tty->termios->c_cflag); } static void rs285_stop(struct tty_struct *tty) { disable_irq(IRQ_CONTX); } static void rs285_start(struct tty_struct *tty) { enable_irq(IRQ_CONTX); } static void rs285_wait_until_sent(struct tty_struct *tty, int timeout) { int orig_jiffies = jiffies; while (*CSR_UARTFLG & 8) { current->state = TASK_INTERRUPTIBLE; schedule_timeout(1); if (signal_pending(current)) break; if (timeout && time_after(jiffies, orig_jiffies + timeout)) break; } current->state = TASK_RUNNING; } static int rs285_open(struct tty_struct *tty, struct file *filp) { int line; MOD_INC_USE_COUNT; line = MINOR(tty->device) - tty->driver.minor_start; if (line) { MOD_DEC_USE_COUNT; return -ENODEV; } tty->driver_data = NULL; if (!rs285_tty) rs285_tty = tty; enable_irq(IRQ_CONRX); rs285_use_count++; return 0; } static void rs285_close(struct tty_struct *tty, struct file *filp) { if (!--rs285_use_count) { rs285_wait_until_sent(tty, 0); disable_irq(IRQ_CONRX); disable_irq(IRQ_CONTX); rs285_tty = NULL; } MOD_DEC_USE_COUNT; } static int __init rs285_init(void) { int baud = B9600; if (machine_is_personal_server()) baud = B57600; rs285_driver.magic = TTY_DRIVER_MAGIC; rs285_driver.driver_name = "serial_21285"; rs285_driver.name = SERIAL_21285_NAME; rs285_driver.major = SERIAL_21285_MAJOR; rs285_driver.minor_start = SERIAL_21285_MINOR; rs285_driver.num = 1; rs285_driver.type = TTY_DRIVER_TYPE_SERIAL; rs285_driver.subtype = SERIAL_TYPE_NORMAL; rs285_driver.init_termios = tty_std_termios; rs285_driver.init_termios.c_cflag = baud | CS8 | CREAD | HUPCL | CLOCAL; rs285_driver.flags = TTY_DRIVER_REAL_RAW; rs285_driver.refcount = &rs285_refcount; rs285_driver.table = rs285_table; rs285_driver.termios = rs285_termios; rs285_driver.termios_locked = rs285_termios_locked; rs285_driver.open = rs285_open; rs285_driver.close = rs285_close; rs285_driver.write = rs285_write; rs285_driver.put_char = rs285_put_char; rs285_driver.write_room = rs285_write_room; rs285_driver.chars_in_buffer = rs285_chars_in_buffer; rs285_driver.flush_buffer = rs285_flush_buffer; rs285_driver.throttle = rs285_throttle; rs285_driver.unthrottle = rs285_unthrottle; rs285_driver.send_xchar = rs285_send_xchar; rs285_driver.set_termios = rs285_set_termios; rs285_driver.stop = rs285_stop; rs285_driver.start = rs285_start; rs285_driver.wait_until_sent = rs285_wait_until_sent; callout_driver = rs285_driver; callout_driver.name = SERIAL_21285_AUXNAME; callout_driver.major = SERIAL_21285_AUXMAJOR; callout_driver.subtype = SERIAL_TYPE_CALLOUT; if (request_irq(IRQ_CONRX, rs285_rx_int, 0, "rs285", NULL)) panic("Couldn't get rx irq for rs285"); if (request_irq(IRQ_CONTX, rs285_tx_int, 0, "rs285", NULL)) panic("Couldn't get tx irq for rs285"); if (tty_register_driver(&rs285_driver)) printk(KERN_ERR "Couldn't register 21285 serial driver\n"); if (tty_register_driver(&callout_driver)) printk(KERN_ERR "Couldn't register 21285 callout driver\n"); return 0; } static void __exit rs285_fini(void) { unsigned long flags; int ret; save_flags(flags); cli(); ret = tty_unregister_driver(&callout_driver); if (ret) printk(KERN_ERR "Unable to unregister 21285 callout driver " "(%d)\n", ret); ret = tty_unregister_driver(&rs285_driver); if (ret) printk(KERN_ERR "Unable to unregister 21285 driver (%d)\n", ret); free_irq(IRQ_CONTX, NULL); free_irq(IRQ_CONRX, NULL); restore_flags(flags); } module_init(rs285_init); module_exit(rs285_fini); #ifdef CONFIG_SERIAL_21285_CONSOLE /************** console driver *****************/ static void rs285_console_write(struct console *co, const char *s, u_int count) { int i; disable_irq(IRQ_CONTX); for (i = 0; i < count; i++) { while (*CSR_UARTFLG & 0x20); *CSR_UARTDR = s[i]; if (s[i] == '\n') { while (*CSR_UARTFLG & 0x20); *CSR_UARTDR = '\r'; } } enable_irq(IRQ_CONTX); } static int rs285_console_wait_key(struct console *co) { int c; disable_irq(IRQ_CONRX); while (*CSR_UARTFLG & 0x10); c = *CSR_UARTDR; enable_irq(IRQ_CONRX); return c; } static kdev_t rs285_console_device(struct console *c) { return MKDEV(SERIAL_21285_MAJOR, SERIAL_21285_MINOR); } static int __init rs285_console_setup(struct console *co, char *options) { int baud = 9600; int bits = 8; int parity = 'n'; int cflag = CREAD | HUPCL | CLOCAL; if (machine_is_personal_server()) baud = 57600; if (options) { char *s = options; baud = simple_strtoul(options, NULL, 10); while (*s >= '0' && *s <= '9') s++; if (*s) parity = *s++; if (*s) bits = *s - '0'; } /* * Now construct a cflag setting. */ switch (baud) { case 1200: cflag |= B1200; break; case 2400: cflag |= B2400; break; case 4800: cflag |= B4800; break; case 9600: cflag |= B9600; break; case 19200: cflag |= B19200; break; case 38400: cflag |= B38400; break; case 57600: cflag |= B57600; break; case 115200: cflag |= B115200; break; default: cflag |= B9600; break; } switch (bits) { case 7: cflag |= CS7; break; default: cflag |= CS8; break; } switch (parity) { case 'o': case 'O': cflag |= PARODD; break; case 'e': case 'E': cflag |= PARENB; break; } co->cflag = cflag; rs285_set_cflag(cflag); rs285_console_write(NULL, "\e[2J\e[Hboot ", 12); if (options) rs285_console_write(NULL, options, strlen(options)); else rs285_console_write(NULL, "no options", 10); rs285_console_write(NULL, "\n", 1); return 0; } static struct console rs285_cons = { name: SERIAL_21285_NAME, write: rs285_console_write, device: rs285_console_device, wait_key: rs285_console_wait_key, setup: rs285_console_setup, flags: CON_PRINTBUFFER, index: -1, }; void __init rs285_console_init(void) { register_console(&rs285_cons); } #endif /* CONFIG_SERIAL_21285_CONSOLE */ MODULE_LICENSE("GPL"); EXPORT_NO_SYMBOLS;