diff options
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..613ccf09dc2 --- /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 = 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, +	.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");  | 
