diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index b575aedb2359f6..5cf21371a81416 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -41,7 +41,6 @@ #include #include #include -#include #include "amba-pl011.h" @@ -266,8 +265,8 @@ struct uart_amba_port { unsigned int old_cr; /* state during shutdown */ unsigned int fixed_baud; /* vendor-set fixed baud rate */ char type[12]; - bool rs485_tx_started; - unsigned long rs485_tx_drain_interval; /* average tx queue drain time in usecs */ + bool rs485_tx_started; + unsigned int rs485_tx_drain_interval; /* usecs */ #ifdef CONFIG_DMA_ENGINE /* DMA stuff */ bool using_tx_dma; @@ -1289,14 +1288,23 @@ static inline bool pl011_dma_rx_running(struct uart_amba_port *uap) #define pl011_dma_flush_buffer NULL #endif -static int pl011_rs485_tx_stop(struct uart_amba_port *uap) +static void pl011_rs485_tx_stop(struct uart_amba_port *uap) { struct uart_port *port = &uap->port; + int i = 0; u32 cr; - /* wait until hardware tx queue is empty */ - while (!pl011_tx_empty(port)) + /* Wait until hardware tx queue is empty */ + while (!pl011_tx_empty(port)) { + if (i == port->fifosize) { + dev_warn(port->dev, + "timeout while draining hardware tx queue\n"); + break; + } + udelay(uap->rs485_tx_drain_interval); + i++; + } if (port->rs485.delay_rts_after_send) mdelay(port->rs485.delay_rts_after_send); @@ -1308,13 +1316,12 @@ static int pl011_rs485_tx_stop(struct uart_amba_port *uap) else cr |= UART011_CR_RTS; - cr &= ~UART011_CR_TXE; /* disable transmitter */ - cr |= UART011_CR_RXE; /* enable receiver */ + /* Disable the transmitter and reenable the transceiver */ + cr &= ~UART011_CR_TXE; + cr |= UART011_CR_RXE; pl011_write(cr, uap, REG_CR); uap->rs485_tx_started = false; - - return 0; } static void pl011_stop_tx(struct uart_port *port) @@ -1449,17 +1456,18 @@ static void pl011_rs485_tx_start(struct uart_amba_port *uap) struct uart_port *port = &uap->port; u32 cr; + /* Enable transmitter */ cr = pl011_read(uap, REG_CR); - cr |= UART011_CR_TXE; /* re-enable transmitter */ + cr |= UART011_CR_TXE; + /* Disable receiver if half-duplex */ if (!(port->rs485.flags & SER_RS485_RX_DURING_TX)) - cr &= ~UART011_CR_RXE; /* disable receiver if half-duplex */ + cr &= ~UART011_CR_RXE; - if (port->rs485.flags & SER_RS485_RTS_ON_SEND) { + if (port->rs485.flags & SER_RS485_RTS_ON_SEND) cr &= ~UART011_CR_RTS; - } else { + else cr |= UART011_CR_RTS; - } pl011_write(cr, uap, REG_CR); @@ -2071,6 +2079,7 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios, unsigned int lcr_h, old_cr; unsigned long flags; unsigned int baud, quot, clkdiv; + unsigned int bits; if (uap->vendor->oversampling) clkdiv = 8; @@ -2121,18 +2130,21 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios, if (uap->fifosize > 1) lcr_h |= UART01x_LCRH_FEN; + bits = tty_get_frame_size(termios->c_cflag); + spin_lock_irqsave(&port->lock, flags); /* * Update the per-port timeout. */ uart_update_timeout(port, termios->c_cflag, baud); - /* Calculate the approximated maximum time it takes to drain the tx - hardware queue with the given baud rate. Then use the half of this - time to get the approximated time for the average case and use this - as the poll interval when waiting for the queue to empty. */ - uap->rs485_tx_drain_interval = jiffies_to_usecs(port->timeout) / - uap->fifosize / 2; + + /* + * Calculate the approximated time it takes to transmit one character + * with the given baud rate. We use this as the poll interval when we + * wait for the tx queue to empty. + */ + uap->rs485_tx_drain_interval = (bits * 1000 * 1000) / baud; pl011_setup_status_masks(port, termios); @@ -2268,17 +2280,16 @@ static int pl011_rs485_config(struct uart_port *port, { struct uart_amba_port *uap = container_of(port, struct uart_amba_port, port); - bool terminate = false; /* pick sane settings if the user hasn't */ - if (!!(rs485->flags & SER_RS485_RTS_ON_SEND) == - !!(rs485->flags & SER_RS485_RTS_AFTER_SEND)) { + if (!(rs485->flags & SER_RS485_RTS_ON_SEND) == + !(rs485->flags & SER_RS485_RTS_AFTER_SEND)) { rs485->flags |= SER_RS485_RTS_ON_SEND; rs485->flags &= ~SER_RS485_RTS_AFTER_SEND; } - - rs485->delay_rts_before_send = min(rs485->delay_rts_before_send, 1000U); - rs485->delay_rts_after_send = min(rs485->delay_rts_after_send, 1000U); + /* clamp the delays to [0, 100ms] */ + rs485->delay_rts_before_send = min(rs485->delay_rts_before_send, 100U); + rs485->delay_rts_after_send = min(rs485->delay_rts_after_send, 100U); memset(rs485->padding, 0, sizeof(rs485->padding)); if (port->rs485.flags & SER_RS485_ENABLED) @@ -2286,19 +2297,16 @@ static int pl011_rs485_config(struct uart_port *port, /* Set new configuration */ port->rs485 = *rs485; + /* Make sure auto RTS is disabled */ if (port->rs485.flags & SER_RS485_ENABLED) { u32 cr = pl011_read(uap, REG_CR); + cr &= ~UART011_CR_RTSEN; pl011_write(cr, uap, REG_CR); - port->status &= ~UPSTAT_AUTORTS; - terminate = !!(rs485->flags & SER_RS485_TERMINATE_BUS); } - if (port->rs485_term_gpio) - gpiod_set_value_cansleep(port->rs485_term_gpio, terminate); - return 0; } @@ -2791,6 +2799,23 @@ static int pl011_find_free_port(void) return -EBUSY; } +static int pl011_get_rs485_mode(struct uart_amba_port *uap) +{ + struct uart_port *port = &uap->port; + struct serial_rs485 *rs485 = &port->rs485; + int ret; + + ret = uart_get_rs485_mode(port); + if (ret) + return ret; + + /* clamp the delays to [0, 100ms] */ + rs485->delay_rts_before_send = min(rs485->delay_rts_before_send, 100U); + rs485->delay_rts_after_send = min(rs485->delay_rts_after_send, 100U); + + return 0; +} + static int pl011_setup_port(struct device *dev, struct uart_amba_port *uap, struct resource *mmiobase, int index) { @@ -2817,7 +2842,7 @@ static int pl011_setup_port(struct device *dev, struct uart_amba_port *uap, uap->port.flags = UPF_BOOT_AUTOCONF; uap->port.line = index; - ret = uart_get_rs485_mode(&uap->port); + ret = pl011_get_rs485_mode(uap); if (ret) return ret; diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 68a0ff6054761a..fc63a0e06ba15f 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -334,39 +334,15 @@ void uart_update_timeout(struct uart_port *port, unsigned int cflag, unsigned int baud) { - unsigned int bits; + unsigned int size; - /* byte size and parity */ - switch (cflag & CSIZE) { - case CS5: - bits = 7; - break; - case CS6: - bits = 8; - break; - case CS7: - bits = 9; - break; - default: - bits = 10; - break; /* CS8 */ - } - - if (cflag & CSTOPB) - bits++; - if (cflag & PARENB) - bits++; - - /* - * The total number of bits to be transmitted in the fifo. - */ - bits = bits * port->fifosize; + size = tty_get_frame_size(cflag) * port->fifosize; /* * Figure the timeout to send the above number of bits. * Add .02 seconds of slop */ - port->timeout = (HZ * bits) / baud + HZ/50; + port->timeout = (HZ * size) / baud + HZ/50; } EXPORT_SYMBOL(uart_update_timeout); diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c index 803da2d111c8c4..7a740a7082eee0 100644 --- a/drivers/tty/tty_ioctl.c +++ b/drivers/tty/tty_ioctl.c @@ -300,6 +300,51 @@ int tty_termios_hw_change(const struct ktermios *a, const struct ktermios *b) } EXPORT_SYMBOL(tty_termios_hw_change); +/** + * tty_get_char_size - get size of a character + * @cflag: termios cflag value + * + * Get the size (in bits) of a character depending on @cflag's %CSIZE + * setting. + */ +unsigned char tty_get_char_size(unsigned int cflag) +{ + switch (cflag & CSIZE) { + case CS5: + return 5; + case CS6: + return 6; + case CS7: + return 7; + case CS8: + default: + return 8; + } +} +EXPORT_SYMBOL_GPL(tty_get_char_size); + +/** + * tty_get_frame_size - get size of a frame + * @cflag: termios cflag value + * + * Get the size (in bits) of a frame depending on @cflag's %CSIZE, %CSTOPB, + * and %PARENB setting. The result is a sum of character size, start and + * stop bits -- one bit each -- second stop bit (if set), and parity bit + * (if set). + */ +unsigned char tty_get_frame_size(unsigned int cflag) +{ + unsigned char bits = 2 + tty_get_char_size(cflag); + + if (cflag & CSTOPB) + bits++; + if (cflag & PARENB) + bits++; + + return bits; +} +EXPORT_SYMBOL_GPL(tty_get_frame_size); + /** * tty_set_termios - update termios values * @tty: tty to update diff --git a/include/linux/tty.h b/include/linux/tty.h index 5972f43b9d5aeb..74c57001d409af 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -540,6 +540,9 @@ static inline speed_t tty_get_baud_rate(struct tty_struct *tty) return tty_termios_baud_rate(&tty->termios); } +unsigned char tty_get_char_size(unsigned int cflag); +unsigned char tty_get_frame_size(unsigned int cflag); + extern void tty_termios_copy_hw(struct ktermios *new, struct ktermios *old); extern int tty_termios_hw_change(const struct ktermios *a, const struct ktermios *b); extern int tty_set_termios(struct tty_struct *tty, struct ktermios *kt);