diff options
Diffstat (limited to 'drivers/serial/v850e_uart.c')
-rw-r--r-- | drivers/serial/v850e_uart.c | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/drivers/serial/v850e_uart.c b/drivers/serial/v850e_uart.c new file mode 100644 index 000000000000..bb482780a41d --- /dev/null +++ b/drivers/serial/v850e_uart.c @@ -0,0 +1,549 @@ +/* + * drivers/serial/v850e_uart.c -- Serial I/O using V850E on-chip UART or UARTB + * + * Copyright (C) 2001,02,03 NEC Electronics Corporation + * Copyright (C) 2001,02,03 Miles Bader <miles@gnu.org> + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file COPYING in the main directory of this + * archive for more details. + * + * Written by Miles Bader <miles@gnu.org> + */ + +/* This driver supports both the original V850E UART interface (called + merely `UART' in the docs) and the newer `UARTB' interface, which is + roughly a superset of the first one. The selection is made at + configure time -- if CONFIG_V850E_UARTB is defined, then UARTB is + presumed, otherwise the old UART -- as these are on-CPU UARTS, a system + can never have both. + + The UARTB interface also has a 16-entry FIFO mode, which is not + yet supported by this driver. */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/console.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/serial.h> +#include <linux/serial_core.h> + +#include <asm/v850e_uart.h> + +/* Initial UART state. This may be overridden by machine-dependent headers. */ +#ifndef V850E_UART_INIT_BAUD +#define V850E_UART_INIT_BAUD 115200 +#endif +#ifndef V850E_UART_INIT_CFLAGS +#define V850E_UART_INIT_CFLAGS (B115200 | CS8 | CREAD) +#endif + +/* A string used for prefixing printed descriptions; since the same UART + macro is actually used on other chips than the V850E. This must be a + constant string. */ +#ifndef V850E_UART_CHIP_NAME +#define V850E_UART_CHIP_NAME "V850E" +#endif + +#define V850E_UART_MINOR_BASE 64 /* First tty minor number */ + + +/* Low-level UART functions. */ + +/* Configure and turn on uart channel CHAN, using the termios `control + modes' bits in CFLAGS, and a baud-rate of BAUD. */ +void v850e_uart_configure (unsigned chan, unsigned cflags, unsigned baud) +{ + int flags; + v850e_uart_speed_t old_speed; + v850e_uart_config_t old_config; + v850e_uart_speed_t new_speed = v850e_uart_calc_speed (baud); + v850e_uart_config_t new_config = v850e_uart_calc_config (cflags); + + /* Disable interrupts while we're twiddling the hardware. */ + local_irq_save (flags); + +#ifdef V850E_UART_PRE_CONFIGURE + V850E_UART_PRE_CONFIGURE (chan, cflags, baud); +#endif + + old_config = V850E_UART_CONFIG (chan); + old_speed = v850e_uart_speed (chan); + + if (! v850e_uart_speed_eq (old_speed, new_speed)) { + /* The baud rate has changed. First, disable the UART. */ + V850E_UART_CONFIG (chan) = V850E_UART_CONFIG_FINI; + old_config = 0; /* Force the uart to be re-initialized. */ + + /* Reprogram the baud-rate generator. */ + v850e_uart_set_speed (chan, new_speed); + } + + if (! (old_config & V850E_UART_CONFIG_ENABLED)) { + /* If we are using the uart for the first time, start by + enabling it, which must be done before turning on any + other bits. */ + V850E_UART_CONFIG (chan) = V850E_UART_CONFIG_INIT; + /* See the initial state. */ + old_config = V850E_UART_CONFIG (chan); + } + + if (new_config != old_config) { + /* Which of the TXE/RXE bits we'll temporarily turn off + before changing other control bits. */ + unsigned temp_disable = 0; + /* Which of the TXE/RXE bits will be enabled. */ + unsigned enable = 0; + unsigned changed_bits = new_config ^ old_config; + + /* Which of RX/TX will be enabled in the new configuration. */ + if (new_config & V850E_UART_CONFIG_RX_BITS) + enable |= (new_config & V850E_UART_CONFIG_RX_ENABLE); + if (new_config & V850E_UART_CONFIG_TX_BITS) + enable |= (new_config & V850E_UART_CONFIG_TX_ENABLE); + + /* Figure out which of RX/TX needs to be disabled; note + that this will only happen if they're not already + disabled. */ + if (changed_bits & V850E_UART_CONFIG_RX_BITS) + temp_disable + |= (old_config & V850E_UART_CONFIG_RX_ENABLE); + if (changed_bits & V850E_UART_CONFIG_TX_BITS) + temp_disable + |= (old_config & V850E_UART_CONFIG_TX_ENABLE); + + /* We have to turn off RX and/or TX mode before changing + any associated control bits. */ + if (temp_disable) + V850E_UART_CONFIG (chan) = old_config & ~temp_disable; + + /* Write the new control bits, while RX/TX are disabled. */ + if (changed_bits & ~enable) + V850E_UART_CONFIG (chan) = new_config & ~enable; + + v850e_uart_config_delay (new_config, new_speed); + + /* Write the final version, with enable bits turned on. */ + V850E_UART_CONFIG (chan) = new_config; + } + + local_irq_restore (flags); +} + + +/* Low-level console. */ + +#ifdef CONFIG_V850E_UART_CONSOLE + +static void v850e_uart_cons_write (struct console *co, + const char *s, unsigned count) +{ + if (count > 0) { + unsigned chan = co->index; + unsigned irq = V850E_UART_TX_IRQ (chan); + int irq_was_enabled, irq_was_pending, flags; + + /* We don't want to get `transmission completed' + interrupts, since we're busy-waiting, so we disable them + while sending (we don't disable interrupts entirely + because sending over a serial line is really slow). We + save the status of the tx interrupt and restore it when + we're done so that using printk doesn't interfere with + normal serial transmission (other than interleaving the + output, of course!). This should work correctly even if + this function is interrupted and the interrupt printks + something. */ + + /* Disable interrupts while fiddling with tx interrupt. */ + local_irq_save (flags); + /* Get current tx interrupt status. */ + irq_was_enabled = v850e_intc_irq_enabled (irq); + irq_was_pending = v850e_intc_irq_pending (irq); + /* Disable tx interrupt if necessary. */ + if (irq_was_enabled) + v850e_intc_disable_irq (irq); + /* Turn interrupts back on. */ + local_irq_restore (flags); + + /* Send characters. */ + while (count > 0) { + int ch = *s++; + + if (ch == '\n') { + /* We don't have the benefit of a tty + driver, so translate NL into CR LF. */ + v850e_uart_wait_for_xmit_ok (chan); + v850e_uart_putc (chan, '\r'); + } + + v850e_uart_wait_for_xmit_ok (chan); + v850e_uart_putc (chan, ch); + + count--; + } + + /* Restore saved tx interrupt status. */ + if (irq_was_enabled) { + /* Wait for the last character we sent to be + completely transmitted (as we'll get an + interrupt interrupt at that point). */ + v850e_uart_wait_for_xmit_done (chan); + /* Clear pending interrupts received due + to our transmission, unless there was already + one pending, in which case we want the + handler to be called. */ + if (! irq_was_pending) + v850e_intc_clear_pending_irq (irq); + /* ... and then turn back on handling. */ + v850e_intc_enable_irq (irq); + } + } +} + +extern struct uart_driver v850e_uart_driver; +static struct console v850e_uart_cons = +{ + .name = "ttyS", + .write = v850e_uart_cons_write, + .device = uart_console_device, + .flags = CON_PRINTBUFFER, + .cflag = V850E_UART_INIT_CFLAGS, + .index = -1, + .data = &v850e_uart_driver, +}; + +void v850e_uart_cons_init (unsigned chan) +{ + v850e_uart_configure (chan, V850E_UART_INIT_CFLAGS, + V850E_UART_INIT_BAUD); + v850e_uart_cons.index = chan; + register_console (&v850e_uart_cons); + printk ("Console: %s on-chip UART channel %d\n", + V850E_UART_CHIP_NAME, chan); +} + +/* This is what the init code actually calls. */ +static int v850e_uart_console_init (void) +{ + v850e_uart_cons_init (V850E_UART_CONSOLE_CHANNEL); + return 0; +} +console_initcall(v850e_uart_console_init); + +#define V850E_UART_CONSOLE &v850e_uart_cons + +#else /* !CONFIG_V850E_UART_CONSOLE */ +#define V850E_UART_CONSOLE 0 +#endif /* CONFIG_V850E_UART_CONSOLE */ + +/* TX/RX interrupt handlers. */ + +static void v850e_uart_stop_tx (struct uart_port *port, unsigned tty_stop); + +void v850e_uart_tx (struct uart_port *port) +{ + struct circ_buf *xmit = &port->info->xmit; + int stopped = uart_tx_stopped (port); + + if (v850e_uart_xmit_ok (port->line)) { + int tx_ch; + + if (port->x_char) { + tx_ch = port->x_char; + port->x_char = 0; + } else if (!uart_circ_empty (xmit) && !stopped) { + tx_ch = xmit->buf[xmit->tail]; + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + } else + goto no_xmit; + + v850e_uart_putc (port->line, tx_ch); + port->icount.tx++; + + if (uart_circ_chars_pending (xmit) < WAKEUP_CHARS) + uart_write_wakeup (port); + } + + no_xmit: + if (uart_circ_empty (xmit) || stopped) + v850e_uart_stop_tx (port, stopped); +} + +static irqreturn_t v850e_uart_tx_irq(int irq, void *data, struct pt_regs *regs) +{ + struct uart_port *port = data; + v850e_uart_tx (port); + return IRQ_HANDLED; +} + +static irqreturn_t v850e_uart_rx_irq(int irq, void *data, struct pt_regs *regs) +{ + struct uart_port *port = data; + unsigned ch_stat = TTY_NORMAL; + unsigned ch = v850e_uart_getc (port->line); + unsigned err = v850e_uart_err (port->line); + + if (err) { + if (err & V850E_UART_ERR_OVERRUN) { + ch_stat = TTY_OVERRUN; + port->icount.overrun++; + } else if (err & V850E_UART_ERR_FRAME) { + ch_stat = TTY_FRAME; + port->icount.frame++; + } else if (err & V850E_UART_ERR_PARITY) { + ch_stat = TTY_PARITY; + port->icount.parity++; + } + } + + port->icount.rx++; + + tty_insert_flip_char (port->info->tty, ch, ch_stat); + tty_schedule_flip (port->info->tty); + + return IRQ_HANDLED; +} + + +/* Control functions for the serial framework. */ + +static void v850e_uart_nop (struct uart_port *port) { } +static int v850e_uart_success (struct uart_port *port) { return 0; } + +static unsigned v850e_uart_tx_empty (struct uart_port *port) +{ + return TIOCSER_TEMT; /* Can't detect. */ +} + +static void v850e_uart_set_mctrl (struct uart_port *port, unsigned mctrl) +{ +#ifdef V850E_UART_SET_RTS + V850E_UART_SET_RTS (port->line, (mctrl & TIOCM_RTS)); +#endif +} + +static unsigned v850e_uart_get_mctrl (struct uart_port *port) +{ + /* We don't support DCD or DSR, so consider them permanently active. */ + int mctrl = TIOCM_CAR | TIOCM_DSR; + + /* We may support CTS. */ +#ifdef V850E_UART_CTS + mctrl |= V850E_UART_CTS(port->line) ? TIOCM_CTS : 0; +#else + mctrl |= TIOCM_CTS; +#endif + + return mctrl; +} + +static void v850e_uart_start_tx (struct uart_port *port, unsigned tty_start) +{ + v850e_intc_disable_irq (V850E_UART_TX_IRQ (port->line)); + v850e_uart_tx (port); + v850e_intc_enable_irq (V850E_UART_TX_IRQ (port->line)); +} + +static void v850e_uart_stop_tx (struct uart_port *port, unsigned tty_stop) +{ + v850e_intc_disable_irq (V850E_UART_TX_IRQ (port->line)); +} + +static void v850e_uart_start_rx (struct uart_port *port) +{ + v850e_intc_enable_irq (V850E_UART_RX_IRQ (port->line)); +} + +static void v850e_uart_stop_rx (struct uart_port *port) +{ + v850e_intc_disable_irq (V850E_UART_RX_IRQ (port->line)); +} + +static void v850e_uart_break_ctl (struct uart_port *port, int break_ctl) +{ + /* Umm, do this later. */ +} + +static int v850e_uart_startup (struct uart_port *port) +{ + int err; + + /* Alloc RX irq. */ + err = request_irq (V850E_UART_RX_IRQ (port->line), v850e_uart_rx_irq, + SA_INTERRUPT, "v850e_uart", port); + if (err) + return err; + + /* Alloc TX irq. */ + err = request_irq (V850E_UART_TX_IRQ (port->line), v850e_uart_tx_irq, + SA_INTERRUPT, "v850e_uart", port); + if (err) { + free_irq (V850E_UART_RX_IRQ (port->line), port); + return err; + } + + v850e_uart_start_rx (port); + + return 0; +} + +static void v850e_uart_shutdown (struct uart_port *port) +{ + /* Disable port interrupts. */ + free_irq (V850E_UART_TX_IRQ (port->line), port); + free_irq (V850E_UART_RX_IRQ (port->line), port); + + /* Turn off xmit/recv enable bits. */ + V850E_UART_CONFIG (port->line) + &= ~(V850E_UART_CONFIG_TX_ENABLE + | V850E_UART_CONFIG_RX_ENABLE); + /* Then reset the channel. */ + V850E_UART_CONFIG (port->line) = 0; +} + +static void +v850e_uart_set_termios (struct uart_port *port, struct termios *termios, + struct termios *old) +{ + unsigned cflags = termios->c_cflag; + + /* Restrict flags to legal values. */ + if ((cflags & CSIZE) != CS7 && (cflags & CSIZE) != CS8) + /* The new value of CSIZE is invalid, use the old value. */ + cflags = (cflags & ~CSIZE) + | (old ? (old->c_cflag & CSIZE) : CS8); + + termios->c_cflag = cflags; + + v850e_uart_configure (port->line, cflags, + uart_get_baud_rate (port, termios, old, + v850e_uart_min_baud(), + v850e_uart_max_baud())); +} + +static const char *v850e_uart_type (struct uart_port *port) +{ + return port->type == PORT_V850E_UART ? "v850e_uart" : 0; +} + +static void v850e_uart_config_port (struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) + port->type = PORT_V850E_UART; +} + +static int +v850e_uart_verify_port (struct uart_port *port, struct serial_struct *ser) +{ + if (ser->type != PORT_UNKNOWN && ser->type != PORT_V850E_UART) + return -EINVAL; + if (ser->irq != V850E_UART_TX_IRQ (port->line)) + return -EINVAL; + return 0; +} + +static struct uart_ops v850e_uart_ops = { + .tx_empty = v850e_uart_tx_empty, + .get_mctrl = v850e_uart_get_mctrl, + .set_mctrl = v850e_uart_set_mctrl, + .start_tx = v850e_uart_start_tx, + .stop_tx = v850e_uart_stop_tx, + .stop_rx = v850e_uart_stop_rx, + .enable_ms = v850e_uart_nop, + .break_ctl = v850e_uart_break_ctl, + .startup = v850e_uart_startup, + .shutdown = v850e_uart_shutdown, + .set_termios = v850e_uart_set_termios, + .type = v850e_uart_type, + .release_port = v850e_uart_nop, + .request_port = v850e_uart_success, + .config_port = v850e_uart_config_port, + .verify_port = v850e_uart_verify_port, +}; + +/* Initialization and cleanup. */ + +static struct uart_driver v850e_uart_driver = { + .owner = THIS_MODULE, + .driver_name = "v850e_uart", + .devfs_name = "tts/", + .dev_name = "ttyS", + .major = TTY_MAJOR, + .minor = V850E_UART_MINOR_BASE, + .nr = V850E_UART_NUM_CHANNELS, + .cons = V850E_UART_CONSOLE, +}; + + +static struct uart_port v850e_uart_ports[V850E_UART_NUM_CHANNELS]; + +static int __init v850e_uart_init (void) +{ + int rval; + + printk (KERN_INFO "%s on-chip UART\n", V850E_UART_CHIP_NAME); + + rval = uart_register_driver (&v850e_uart_driver); + if (rval == 0) { + unsigned chan; + + for (chan = 0; chan < V850E_UART_NUM_CHANNELS; chan++) { + struct uart_port *port = &v850e_uart_ports[chan]; + + memset (port, 0, sizeof *port); + + port->ops = &v850e_uart_ops; + port->line = chan; + port->iotype = SERIAL_IO_MEM; + port->flags = UPF_BOOT_AUTOCONF; + + /* We actually use multiple IRQs, but the serial + framework seems to mainly use this for + informational purposes anyway. Here we use the TX + irq. */ + port->irq = V850E_UART_TX_IRQ (chan); + + /* The serial framework doesn't really use these + membase/mapbase fields for anything useful, but + it requires that they be something non-zero to + consider the port `valid', and also uses them + for informational purposes. */ + port->membase = (void *)V850E_UART_BASE_ADDR (chan); + port->mapbase = V850E_UART_BASE_ADDR (chan); + + /* The framework insists on knowing the uart's master + clock freq, though it doesn't seem to do anything + useful for us with it. We must make it at least + higher than (the maximum baud rate * 16), otherwise + the framework will puke during its internal + calculations, and force the baud rate to be 9600. + To be accurate though, just repeat the calculation + we use when actually setting the speed. */ + port->uartclk = v850e_uart_max_clock() * 16; + + uart_add_one_port (&v850e_uart_driver, port); + } + } + + return rval; +} + +static void __exit v850e_uart_exit (void) +{ + unsigned chan; + + for (chan = 0; chan < V850E_UART_NUM_CHANNELS; chan++) + uart_remove_one_port (&v850e_uart_driver, + &v850e_uart_ports[chan]); + + uart_unregister_driver (&v850e_uart_driver); +} + +module_init (v850e_uart_init); +module_exit (v850e_uart_exit); + +MODULE_AUTHOR ("Miles Bader"); +MODULE_DESCRIPTION ("NEC " V850E_UART_CHIP_NAME " on-chip UART"); +MODULE_LICENSE ("GPL"); |