diff options
Diffstat (limited to 'drivers/serial/8250.c')
-rw-r--r-- | drivers/serial/8250.c | 39 |
1 files changed, 38 insertions, 1 deletions
diff --git a/drivers/serial/8250.c b/drivers/serial/8250.c index a181ccf46281..f16da5982358 100644 --- a/drivers/serial/8250.c +++ b/drivers/serial/8250.c @@ -38,6 +38,7 @@ #include <linux/serial_8250.h> #include <linux/nmi.h> #include <linux/mutex.h> +#include <linux/gpio.h> #include <asm/io.h> #include <asm/irq.h> @@ -59,6 +60,8 @@ static unsigned int nr_uarts = CONFIG_SERIAL_8250_RUNTIME_UARTS; static struct uart_driver serial8250_reg; +DECLARE_WAIT_QUEUE_HEAD(thre_wait); + static int serial_index(struct uart_port *port) { return (serial8250_reg.minor - 64) + port->line; @@ -1225,7 +1228,10 @@ static void autoconfig_irq(struct uart_8250_port *up) static inline void __stop_tx(struct uart_8250_port *p) { - if (p->ier & UART_IER_THRI) { + int status = serial_in(p, UART_LSR); + // only turn off THRE interrupt if THRE is *currently* asserted + // (we still want to catch it a final time after the FIFO empties) + if ((p->ier & UART_IER_THRI) && (status & UART_LSR_THRE)) { p->ier &= ~UART_IER_THRI; serial_out(p, UART_IER, p->ier); } @@ -1527,6 +1533,7 @@ static irqreturn_t serial8250_interrupt(int irq, void *dev_id) DEBUG_INTR("end.\n"); + wake_up(&thre_wait); return IRQ_RETVAL(handled); } @@ -2526,6 +2533,35 @@ serial8250_type(struct uart_port *port) return uart_config[type].name; } +static int serial8250_ioctl(struct uart_port *port, unsigned int cmd, unsigned long arg) { + struct uart_8250_port *up = (struct uart_8250_port *)port; + unsigned long flags; + int ret = -ENOIOCTLCMD; + // kernel-space RS485 drain-and-switch hack + if (cmd == TIOCSERWAITTEMT) { + // wait for kernel buffers and UART FIFO to both empty + struct circ_buf *xmit = &up->port.info->xmit; + wait_event_interruptible( + thre_wait, + uart_circ_empty(xmit) && + (serial_in(up, UART_LSR) & UART_LSR_THRE)); + // spin until TEMT (transmit shift register empty) + spin_lock_irqsave(&up->port.lock, flags); + wait_for_xmitr(up, BOTH_EMPTY); + if (arg != 0) { + // turn off RS485 DE pin + gpio_set_value(arg, 0); + } + // grab any phantom char seen on RX when transceiver switches + (void) serial_inp(up, UART_RX); + // enable read + up->port.ignore_status_mask &= ~UART_LSR_DR; + spin_unlock_irqrestore(&up->port.lock, flags); + return 0; + } + return ret; +} + static struct uart_ops serial8250_pops = { .tx_empty = serial8250_tx_empty, .set_mctrl = serial8250_set_mctrl, @@ -2544,6 +2580,7 @@ static struct uart_ops serial8250_pops = { .request_port = serial8250_request_port, .config_port = serial8250_config_port, .verify_port = serial8250_verify_port, + .ioctl = serial8250_ioctl, #ifdef CONFIG_CONSOLE_POLL .poll_get_char = serial8250_get_poll_char, .poll_put_char = serial8250_put_poll_char, |