diff options
author | Chris Metcalf <cmetcalf@tilera.com> | 2013-08-12 14:11:44 -0400 |
---|---|---|
committer | Chris Metcalf <cmetcalf@tilera.com> | 2013-09-03 14:50:40 -0400 |
commit | b5c6c1a72afcc416c11ad932589054dcd3125782 (patch) | |
tree | 4ea65027e6cb9fd4951c5091b49252c3d4d75b2b /drivers/tty/serial/tilegx.c | |
parent | 6ec006ede5e0526c20cd7ed5e20df637ea592b1f (diff) |
tilegx: Add tty serial support for TILE-Gx on-chip UART
Signed-off-by: Chris Metcalf <cmetcalf@tilera.com>
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/tty/serial/tilegx.c')
-rw-r--r-- | drivers/tty/serial/tilegx.c | 708 |
1 files changed, 708 insertions, 0 deletions
diff --git a/drivers/tty/serial/tilegx.c b/drivers/tty/serial/tilegx.c new file mode 100644 index 00000000000..f92d7e6bd87 --- /dev/null +++ b/drivers/tty/serial/tilegx.c @@ -0,0 +1,708 @@ +/* + * Copyright 2013 Tilera Corporation. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for + * more details. + * + * TILEGx UART driver. + */ + +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/serial_core.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> + +#include <gxio/common.h> +#include <gxio/iorpc_globals.h> +#include <gxio/iorpc_uart.h> +#include <gxio/kiorpc.h> + +#include <hv/drv_uart_intf.h> + +/* + * Use device name ttyS, major 4, minor 64-65. + * This is the usual serial port name, 8250 conventional range. + */ +#define TILEGX_UART_MAJOR TTY_MAJOR +#define TILEGX_UART_MINOR 64 +#define TILEGX_UART_NAME "ttyS" +#define DRIVER_NAME_STRING "TILEGx_Serial" +#define TILEGX_UART_REF_CLK 125000000; /* REF_CLK is always 125 MHz. */ + +struct tile_uart_port { + /* UART port. */ + struct uart_port uart; + + /* GXIO device context. */ + gxio_uart_context_t context; + + /* UART access mutex. */ + struct mutex mutex; + + /* CPU receiving interrupts. */ + int irq_cpu; +}; + +static struct tile_uart_port tile_uart_ports[TILEGX_UART_NR]; +static struct uart_driver tilegx_uart_driver; + + +/* + * Read UART rx fifo, and insert the chars into tty buffer. + */ +static void receive_chars(struct tile_uart_port *tile_uart, + struct tty_struct *tty) +{ + int i; + char c; + UART_FIFO_COUNT_t count; + gxio_uart_context_t *context = &tile_uart->context; + struct tty_port *port = tty->port; + + count.word = gxio_uart_read(context, UART_FIFO_COUNT); + for (i = 0; i < count.rfifo_count; i++) { + c = (char)gxio_uart_read(context, UART_RECEIVE_DATA); + tty_insert_flip_char(port, c, TTY_NORMAL); + } +} + + +/* + * Drain the Rx FIFO, called by interrupt handler. + */ +static void handle_receive(struct tile_uart_port *tile_uart) +{ + struct tty_port *port = &tile_uart->uart.state->port; + struct tty_struct *tty = tty_port_tty_get(port); + gxio_uart_context_t *context = &tile_uart->context; + + if (!tty) + return; + + /* First read UART rx fifo. */ + receive_chars(tile_uart, tty); + + /* Reset RFIFO_WE interrupt. */ + gxio_uart_write(context, UART_INTERRUPT_STATUS, + UART_INTERRUPT_MASK__RFIFO_WE_MASK); + + /* Final read, if any chars comes between the first read and + * the interrupt reset. + */ + receive_chars(tile_uart, tty); + + spin_unlock(&tile_uart->uart.lock); + tty_flip_buffer_push(port); + spin_lock(&tile_uart->uart.lock); + tty_kref_put(tty); +} + + +/* + * Push one char to UART Write FIFO. + * Return 0 on success, -1 if write filo is full. + */ +static int tilegx_putchar(gxio_uart_context_t *context, char c) +{ + UART_FLAG_t flag; + flag.word = gxio_uart_read(context, UART_FLAG); + if (flag.wfifo_full) + return -1; + + gxio_uart_write(context, UART_TRANSMIT_DATA, (unsigned long)c); + return 0; +} + + +/* + * Send chars to UART Write FIFO; called by interrupt handler. + */ +static void handle_transmit(struct tile_uart_port *tile_uart) +{ + unsigned char ch; + struct uart_port *port; + struct circ_buf *xmit; + gxio_uart_context_t *context = &tile_uart->context; + + /* First reset WFIFO_RE interrupt. */ + gxio_uart_write(context, UART_INTERRUPT_STATUS, + UART_INTERRUPT_MASK__WFIFO_RE_MASK); + + port = &tile_uart->uart; + xmit = &port->state->xmit; + if (port->x_char) { + if (tilegx_putchar(context, port->x_char)) + return; + port->x_char = 0; + port->icount.tx++; + } + + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) + return; + + while (!uart_circ_empty(xmit)) { + ch = xmit->buf[xmit->tail]; + if (tilegx_putchar(context, ch)) + break; + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + } + + /* Reset WFIFO_RE interrupt. */ + gxio_uart_write(context, UART_INTERRUPT_STATUS, + UART_INTERRUPT_MASK__WFIFO_RE_MASK); + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); +} + + +/* + * UART Interrupt handler. + */ +static irqreturn_t tilegx_interrupt(int irq, void *dev_id) +{ + unsigned long flags; + UART_INTERRUPT_STATUS_t intr_stat; + struct tile_uart_port *tile_uart; + gxio_uart_context_t *context; + struct uart_port *port = dev_id; + irqreturn_t ret = IRQ_NONE; + + spin_lock_irqsave(&port->lock, flags); + + tile_uart = container_of(port, struct tile_uart_port, uart); + context = &tile_uart->context; + intr_stat.word = gxio_uart_read(context, UART_INTERRUPT_STATUS); + + if (intr_stat.rfifo_we) { + handle_receive(tile_uart); + ret = IRQ_HANDLED; + } + if (intr_stat.wfifo_re) { + handle_transmit(tile_uart); + ret = IRQ_HANDLED; + } + + spin_unlock_irqrestore(&port->lock, flags); + return ret; +} + + +/* + * Return TIOCSER_TEMT when transmitter FIFO is empty. + */ +static u_int tilegx_tx_empty(struct uart_port *port) +{ + int ret; + UART_FLAG_t flag; + struct tile_uart_port *tile_uart; + gxio_uart_context_t *context; + + tile_uart = container_of(port, struct tile_uart_port, uart); + if (!mutex_trylock(&tile_uart->mutex)) + return 0; + context = &tile_uart->context; + + flag.word = gxio_uart_read(context, UART_FLAG); + ret = (flag.wfifo_empty) ? TIOCSER_TEMT : 0; + mutex_unlock(&tile_uart->mutex); + + return ret; +} + + +/* + * Set state of the modem control output lines. + */ +static void tilegx_set_mctrl(struct uart_port *port, u_int mctrl) +{ + /* N/A */ +} + + +/* + * Get state of the modem control input lines. + */ +static u_int tilegx_get_mctrl(struct uart_port *port) +{ + return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR; +} + + +/* + * Stop transmitting. + */ +static void tilegx_stop_tx(struct uart_port *port) +{ + /* N/A */ +} + + +/* + * Start transmitting. + */ +static void tilegx_start_tx(struct uart_port *port) +{ + unsigned char ch; + struct circ_buf *xmit; + struct tile_uart_port *tile_uart; + gxio_uart_context_t *context; + + tile_uart = container_of(port, struct tile_uart_port, uart); + if (!mutex_trylock(&tile_uart->mutex)) + return; + context = &tile_uart->context; + xmit = &port->state->xmit; + if (port->x_char) { + if (tilegx_putchar(context, port->x_char)) + return; + port->x_char = 0; + port->icount.tx++; + } + + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { + mutex_unlock(&tile_uart->mutex); + return; + } + + while (!uart_circ_empty(xmit)) { + ch = xmit->buf[xmit->tail]; + if (tilegx_putchar(context, ch)) + break; + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + } + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); + + mutex_unlock(&tile_uart->mutex); +} + + +/* + * Stop receiving - port is in process of being closed. + */ +static void tilegx_stop_rx(struct uart_port *port) +{ + int err; + struct tile_uart_port *tile_uart; + gxio_uart_context_t *context; + int cpu; + + tile_uart = container_of(port, struct tile_uart_port, uart); + if (!mutex_trylock(&tile_uart->mutex)) + return; + + context = &tile_uart->context; + cpu = tile_uart->irq_cpu; + err = gxio_uart_cfg_interrupt(context, cpu_x(cpu), cpu_y(cpu), + KERNEL_PL, -1); + mutex_unlock(&tile_uart->mutex); +} + + +/* + * Enable modem status interrupts. + */ +static void tilegx_enable_ms(struct uart_port *port) +{ + /* N/A */ +} + +/* + * Control the transmission of a break signal. + */ +static void tilegx_break_ctl(struct uart_port *port, int break_state) +{ + /* N/A */ +} + + +/* + * Perform initialization and enable port for reception. + */ +static int tilegx_startup(struct uart_port *port) +{ + struct tile_uart_port *tile_uart; + gxio_uart_context_t *context; + int ret = 0; + int cpu = raw_smp_processor_id(); /* pick an arbitrary cpu */ + + tile_uart = container_of(port, struct tile_uart_port, uart); + if (mutex_lock_interruptible(&tile_uart->mutex)) + return -EBUSY; + context = &tile_uart->context; + + /* Now open the hypervisor device if we haven't already. */ + if (context->fd < 0) { + UART_INTERRUPT_MASK_t intr_mask; + + /* Initialize UART device. */ + ret = gxio_uart_init(context, port->line); + if (ret) { + ret = -ENXIO; + goto err; + } + + /* Create our IRQs. */ + port->irq = create_irq(); + if (port->irq < 0) + goto err_uart_dest; + tile_irq_activate(port->irq, TILE_IRQ_PERCPU); + + /* Register our IRQs. */ + ret = request_irq(port->irq, tilegx_interrupt, 0, + tilegx_uart_driver.driver_name, port); + if (ret) + goto err_dest_irq; + + /* Request that the hardware start sending us interrupts. */ + tile_uart->irq_cpu = cpu; + ret = gxio_uart_cfg_interrupt(context, cpu_x(cpu), cpu_y(cpu), + KERNEL_PL, port->irq); + if (ret) + goto err_free_irq; + + /* Enable UART Tx/Rx Interrupt. */ + intr_mask.word = gxio_uart_read(context, UART_INTERRUPT_MASK); + intr_mask.wfifo_re = 0; + intr_mask.rfifo_we = 0; + gxio_uart_write(context, UART_INTERRUPT_MASK, intr_mask.word); + + /* Reset the Tx/Rx interrupt in case it's set. */ + gxio_uart_write(context, UART_INTERRUPT_STATUS, + UART_INTERRUPT_MASK__WFIFO_RE_MASK | + UART_INTERRUPT_MASK__RFIFO_WE_MASK); + } + + mutex_unlock(&tile_uart->mutex); + return ret; + +err_free_irq: + free_irq(port->irq, port); +err_dest_irq: + destroy_irq(port->irq); +err_uart_dest: + gxio_uart_destroy(context); + ret = -ENXIO; +err: + mutex_unlock(&tile_uart->mutex); + return ret; +} + + +/* + * Release kernel resources if it is the last close, disable the port, + * free IRQ and close the port. + */ +static void tilegx_shutdown(struct uart_port *port) +{ + int err; + UART_INTERRUPT_MASK_t intr_mask; + struct tile_uart_port *tile_uart; + gxio_uart_context_t *context; + int cpu; + + tile_uart = container_of(port, struct tile_uart_port, uart); + if (mutex_lock_interruptible(&tile_uart->mutex)) + return; + context = &tile_uart->context; + + /* Disable UART Tx/Rx Interrupt. */ + intr_mask.word = gxio_uart_read(context, UART_INTERRUPT_MASK); + intr_mask.wfifo_re = 1; + intr_mask.rfifo_we = 1; + gxio_uart_write(context, UART_INTERRUPT_MASK, intr_mask.word); + + /* Request that the hardware stop sending us interrupts. */ + cpu = tile_uart->irq_cpu; + err = gxio_uart_cfg_interrupt(context, cpu_x(cpu), cpu_y(cpu), + KERNEL_PL, -1); + + if (port->irq > 0) { + free_irq(port->irq, port); + destroy_irq(port->irq); + port->irq = 0; + } + + gxio_uart_destroy(context); + + mutex_unlock(&tile_uart->mutex); +} + + +/* + * Flush the buffer. + */ +static void tilegx_flush_buffer(struct uart_port *port) +{ + /* N/A */ +} + + +/* + * Change the port parameters. + */ +static void tilegx_set_termios(struct uart_port *port, + struct ktermios *termios, struct ktermios *old) +{ + int err; + UART_DIVISOR_t divisor; + UART_TYPE_t type; + unsigned int baud; + struct tile_uart_port *tile_uart; + gxio_uart_context_t *context; + + tile_uart = container_of(port, struct tile_uart_port, uart); + if (!mutex_trylock(&tile_uart->mutex)) + return; + context = &tile_uart->context; + + /* Open the hypervisor device if we haven't already. */ + if (context->fd < 0) { + err = gxio_uart_init(context, port->line); + if (err) { + mutex_unlock(&tile_uart->mutex); + return; + } + } + + divisor.word = gxio_uart_read(context, UART_DIVISOR); + type.word = gxio_uart_read(context, UART_TYPE); + + /* Divisor. */ + baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16); + divisor.divisor = uart_get_divisor(port, baud); + + /* Byte size. */ + if ((termios->c_cflag & CSIZE) == CS7) + type.dbits = UART_TYPE__DBITS_VAL_SEVEN_DBITS; + else + type.dbits = UART_TYPE__DBITS_VAL_EIGHT_DBITS; + + /* Parity. */ + if (termios->c_cflag & PARENB) { + /* Mark or Space parity. */ + if (termios->c_cflag & CMSPAR) + if (termios->c_cflag & PARODD) + type.ptype = UART_TYPE__PTYPE_VAL_MARK; + else + type.ptype = UART_TYPE__PTYPE_VAL_SPACE; + else if (termios->c_cflag & PARODD) + type.ptype = UART_TYPE__PTYPE_VAL_ODD; + else + type.ptype = UART_TYPE__PTYPE_VAL_EVEN; + } else + type.ptype = UART_TYPE__PTYPE_VAL_NONE; + + /* Stop bits. */ + if (termios->c_cflag & CSTOPB) + type.sbits = UART_TYPE__SBITS_VAL_TWO_SBITS; + else + type.sbits = UART_TYPE__SBITS_VAL_ONE_SBITS; + + /* Set the uart paramters. */ + gxio_uart_write(context, UART_DIVISOR, divisor.word); + gxio_uart_write(context, UART_TYPE, type.word); + + mutex_unlock(&tile_uart->mutex); +} + + +/* + * Return string describing the specified port. + */ +static const char *tilegx_type(struct uart_port *port) +{ + return port->type == PORT_TILEGX ? DRIVER_NAME_STRING : NULL; +} + + +/* + * Release the resources being used by 'port'. + */ +static void tilegx_release_port(struct uart_port *port) +{ + /* Nothing to release. */ +} + + +/* + * Request the resources being used by 'port'. + */ +static int tilegx_request_port(struct uart_port *port) +{ + /* Always present. */ + return 0; +} + + +/* + * Configure/autoconfigure the port. + */ +static void tilegx_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) + port->type = PORT_TILEGX; +} + + +/* + * Verify the new serial_struct (for TIOCSSERIAL). + */ +static int tilegx_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + if ((ser->type != PORT_UNKNOWN) && (ser->type != PORT_TILEGX)) + return -EINVAL; + + return 0; +} + +#ifdef CONFIG_CONSOLE_POLL + +/* + * Console polling routines for writing and reading from the uart while + * in an interrupt or debug context. + */ + +static int tilegx_poll_get_char(struct uart_port *port) +{ + UART_FIFO_COUNT_t count; + gxio_uart_context_t *context; + struct tile_uart_port *tile_uart; + + tile_uart = container_of(port, struct tile_uart_port, uart); + context = &tile_uart->context; + count.word = gxio_uart_read(context, UART_FIFO_COUNT); + if (count.rfifo_count == 0) + return NO_POLL_CHAR; + return (char)gxio_uart_read(context, UART_RECEIVE_DATA); +} + +static void tilegx_poll_put_char(struct uart_port *port, unsigned char c) +{ + gxio_uart_context_t *context; + struct tile_uart_port *tile_uart; + + tile_uart = container_of(port, struct tile_uart_port, uart); + context = &tile_uart->context; + gxio_uart_write(context, UART_TRANSMIT_DATA, (unsigned long)c); +} + +#endif /* CONFIG_CONSOLE_POLL */ + + +static const struct uart_ops tilegx_ops = { + .tx_empty = tilegx_tx_empty, + .set_mctrl = tilegx_set_mctrl, + .get_mctrl = tilegx_get_mctrl, + .stop_tx = tilegx_stop_tx, + .start_tx = tilegx_start_tx, + .stop_rx = tilegx_stop_rx, + .enable_ms = tilegx_enable_ms, + .break_ctl = tilegx_break_ctl, + .startup = tilegx_startup, + .shutdown = tilegx_shutdown, + .flush_buffer = tilegx_flush_buffer, + .set_termios = tilegx_set_termios, + .type = tilegx_type, + .release_port = tilegx_release_port, + .request_port = tilegx_request_port, + .config_port = tilegx_config_port, + .verify_port = tilegx_verify_port, +#ifdef CONFIG_CONSOLE_POLL + .poll_get_char = tilegx_poll_get_char, + .poll_put_char = tilegx_poll_put_char, +#endif +}; + + +static void tilegx_init_ports(void) +{ + int i; + struct uart_port *port; + + for (i = 0; i < TILEGX_UART_NR; i++) { + port = &tile_uart_ports[i].uart; + port->ops = &tilegx_ops; + port->line = i; + port->type = PORT_TILEGX; + port->uartclk = TILEGX_UART_REF_CLK; + port->flags = UPF_BOOT_AUTOCONF; + + tile_uart_ports[i].context.fd = -1; + mutex_init(&tile_uart_ports[i].mutex); + } +} + + +static struct uart_driver tilegx_uart_driver = { + .owner = THIS_MODULE, + .driver_name = DRIVER_NAME_STRING, + .dev_name = TILEGX_UART_NAME, + .major = TILEGX_UART_MAJOR, + .minor = TILEGX_UART_MINOR, + .nr = TILEGX_UART_NR, +}; + + +static int __init tilegx_init(void) +{ + int i; + int ret; + struct tty_driver *tty_drv; + + ret = uart_register_driver(&tilegx_uart_driver); + if (ret) + return ret; + tty_drv = tilegx_uart_driver.tty_driver; + tty_drv->init_termios.c_cflag = B115200 | CS8 | CREAD | HUPCL | CLOCAL; + tty_drv->init_termios.c_ispeed = 115200; + tty_drv->init_termios.c_ospeed = 115200; + + tilegx_init_ports(); + + for (i = 0; i < TILEGX_UART_NR; i++) { + struct uart_port *port = &tile_uart_ports[i].uart; + ret = uart_add_one_port(&tilegx_uart_driver, port); + } + + return 0; +} + + +static void __exit tilegx_exit(void) +{ + int i; + struct uart_port *port; + + for (i = 0; i < TILEGX_UART_NR; i++) { + port = &tile_uart_ports[i].uart; + uart_remove_one_port(&tilegx_uart_driver, port); + } + + uart_unregister_driver(&tilegx_uart_driver); +} + + +module_init(tilegx_init); +module_exit(tilegx_exit); + +MODULE_AUTHOR("Tilera Corporation"); +MODULE_DESCRIPTION("TILEGx serial port driver"); +MODULE_LICENSE("GPL"); |