| /* | |
| * 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); | |
| } | |
| /* | |
| * 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 = irq_alloc_hwirq(-1); | |
| if (!port->irq) | |
| 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: | |
| irq_free_hwirq(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); | |
| irq_free_hwirq(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, | |
| .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"); |