You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
698 lines
16 KiB
698 lines
16 KiB
/* |
|
* 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");
|
|
|