| /* |
| * 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); |
| |
| 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) |
| { |
| 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) |
| { |
| 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", |
| .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 = UPIO_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"); |