diff options
author | Daniel Golle <daniel@makrotopia.org> | 2020-02-21 22:23:31 +0100 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2020-03-06 13:26:26 +0100 |
commit | 9be1064fe524b396cca449b2fe4252b7919a245f (patch) | |
tree | df938963d6007ba029a80a40ecf8b8487b9c0477 | |
parent | 9fa3c4b1fa379f2b55859c6d27ba0563aac06516 (diff) |
serial: ar933x_uart: add RS485 support
Emulate half-duplex operation and use mctrl_gpio to add support for
RS485 tranceiver with transmit/receive switch hooked to RTS GPIO line.
This is needed to make use of the RS485 port found on Teltonika RUT955.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://lore.kernel.org/r/20200221212331.GA21467@makrotopia.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/tty/serial/Kconfig | 1 | ||||
-rw-r--r-- | drivers/tty/serial/ar933x_uart.c | 113 |
2 files changed, 108 insertions, 6 deletions
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 880b96201530..e71af33f8d80 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1269,6 +1269,7 @@ config SERIAL_AR933X tristate "AR933X serial port support" depends on HAVE_CLK && ATH79 select SERIAL_CORE + select SERIAL_MCTRL_GPIO if GPIOLIB help If you have an Atheros AR933X SOC based board and want to use the built-in UART of the SoC, say Y to this option. diff --git a/drivers/tty/serial/ar933x_uart.c b/drivers/tty/serial/ar933x_uart.c index ea12f10610b6..7e7f1398019f 100644 --- a/drivers/tty/serial/ar933x_uart.c +++ b/drivers/tty/serial/ar933x_uart.c @@ -13,6 +13,7 @@ #include <linux/console.h> #include <linux/sysrq.h> #include <linux/delay.h> +#include <linux/gpio/consumer.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/of_platform.h> @@ -29,6 +30,8 @@ #include <asm/mach-ath79/ar933x_uart.h> +#include "serial_mctrl_gpio.h" + #define DRIVER_NAME "ar933x-uart" #define AR933X_UART_MAX_SCALE 0xff @@ -47,6 +50,8 @@ struct ar933x_uart_port { unsigned int min_baud; unsigned int max_baud; struct clk *clk; + struct mctrl_gpios *gpios; + struct gpio_desc *rts_gpiod; }; static inline unsigned int ar933x_uart_read(struct ar933x_uart_port *up, @@ -100,6 +105,18 @@ static inline void ar933x_uart_stop_tx_interrupt(struct ar933x_uart_port *up) ar933x_uart_write(up, AR933X_UART_INT_EN_REG, up->ier); } +static inline void ar933x_uart_start_rx_interrupt(struct ar933x_uart_port *up) +{ + up->ier |= AR933X_UART_INT_RX_VALID; + ar933x_uart_write(up, AR933X_UART_INT_EN_REG, up->ier); +} + +static inline void ar933x_uart_stop_rx_interrupt(struct ar933x_uart_port *up) +{ + up->ier &= ~AR933X_UART_INT_RX_VALID; + ar933x_uart_write(up, AR933X_UART_INT_EN_REG, up->ier); +} + static inline void ar933x_uart_putc(struct ar933x_uart_port *up, int ch) { unsigned int rdata; @@ -125,11 +142,21 @@ static unsigned int ar933x_uart_tx_empty(struct uart_port *port) static unsigned int ar933x_uart_get_mctrl(struct uart_port *port) { - return TIOCM_CAR; + struct ar933x_uart_port *up = + container_of(port, struct ar933x_uart_port, port); + int ret = TIOCM_CTS | TIOCM_DSR | TIOCM_CAR; + + mctrl_gpio_get(up->gpios, &ret); + + return ret; } static void ar933x_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) { + struct ar933x_uart_port *up = + container_of(port, struct ar933x_uart_port, port); + + mctrl_gpio_set(up->gpios, mctrl); } static void ar933x_uart_start_tx(struct uart_port *port) @@ -140,6 +167,37 @@ static void ar933x_uart_start_tx(struct uart_port *port) ar933x_uart_start_tx_interrupt(up); } +static void ar933x_uart_wait_tx_complete(struct ar933x_uart_port *up) +{ + unsigned int status; + unsigned int timeout = 60000; + + /* Wait up to 60ms for the character(s) to be sent. */ + do { + status = ar933x_uart_read(up, AR933X_UART_CS_REG); + if (--timeout == 0) + break; + udelay(1); + } while (status & AR933X_UART_CS_TX_BUSY); + + if (timeout == 0) + dev_err(up->port.dev, "waiting for TX timed out\n"); +} + +static void ar933x_uart_rx_flush(struct ar933x_uart_port *up) +{ + unsigned int status; + + /* clear RX_VALID interrupt */ + ar933x_uart_write(up, AR933X_UART_INT_REG, AR933X_UART_INT_RX_VALID); + + /* remove characters from the RX FIFO */ + do { + ar933x_uart_write(up, AR933X_UART_DATA_REG, AR933X_UART_DATA_RX_CSR); + status = ar933x_uart_read(up, AR933X_UART_DATA_REG); + } while (status & AR933X_UART_DATA_RX_CSR); +} + static void ar933x_uart_stop_tx(struct uart_port *port) { struct ar933x_uart_port *up = @@ -153,8 +211,7 @@ static void ar933x_uart_stop_rx(struct uart_port *port) struct ar933x_uart_port *up = container_of(port, struct ar933x_uart_port, port); - up->ier &= ~AR933X_UART_INT_RX_VALID; - ar933x_uart_write(up, AR933X_UART_INT_EN_REG, up->ier); + ar933x_uart_stop_rx_interrupt(up); } static void ar933x_uart_break_ctl(struct uart_port *port, int break_state) @@ -336,11 +393,20 @@ static void ar933x_uart_rx_chars(struct ar933x_uart_port *up) static void ar933x_uart_tx_chars(struct ar933x_uart_port *up) { struct circ_buf *xmit = &up->port.state->xmit; + struct serial_rs485 *rs485conf = &up->port.rs485; int count; + bool half_duplex_send = false; if (uart_tx_stopped(&up->port)) return; + if ((rs485conf->flags & SER_RS485_ENABLED) && + (up->port.x_char || !uart_circ_empty(xmit))) { + ar933x_uart_stop_rx_interrupt(up); + gpiod_set_value(up->rts_gpiod, !!(rs485conf->flags & SER_RS485_RTS_ON_SEND)); + half_duplex_send = true; + } + count = up->port.fifosize; do { unsigned int rdata; @@ -368,8 +434,14 @@ static void ar933x_uart_tx_chars(struct ar933x_uart_port *up) if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(&up->port); - if (!uart_circ_empty(xmit)) + if (!uart_circ_empty(xmit)) { ar933x_uart_start_tx_interrupt(up); + } else if (half_duplex_send) { + ar933x_uart_wait_tx_complete(up); + ar933x_uart_rx_flush(up); + ar933x_uart_start_rx_interrupt(up); + gpiod_set_value(up->rts_gpiod, !!(rs485conf->flags & SER_RS485_RTS_AFTER_SEND)); + } } static irqreturn_t ar933x_uart_interrupt(int irq, void *dev_id) @@ -427,8 +499,7 @@ static int ar933x_uart_startup(struct uart_port *port) AR933X_UART_CS_TX_READY_ORIDE | AR933X_UART_CS_RX_READY_ORIDE); /* Enable RX interrupts */ - up->ier = AR933X_UART_INT_RX_VALID; - ar933x_uart_write(up, AR933X_UART_INT_EN_REG, up->ier); + ar933x_uart_start_rx_interrupt(up); spin_unlock_irqrestore(&up->port.lock, flags); @@ -511,6 +582,21 @@ static const struct uart_ops ar933x_uart_ops = { .verify_port = ar933x_uart_verify_port, }; +static int ar933x_config_rs485(struct uart_port *port, + struct serial_rs485 *rs485conf) +{ + struct ar933x_uart_port *up = + container_of(port, struct ar933x_uart_port, port); + + if ((rs485conf->flags & SER_RS485_ENABLED) && + !up->rts_gpiod) { + dev_err(port->dev, "RS485 needs rts-gpio\n"); + return 1; + } + port->rs485 = *rs485conf; + return 0; +} + #ifdef CONFIG_SERIAL_AR933X_CONSOLE static struct ar933x_uart_port * ar933x_console_ports[CONFIG_SERIAL_AR933X_NR_UARTS]; @@ -680,6 +766,8 @@ static int ar933x_uart_probe(struct platform_device *pdev) goto err_disable_clk; } + uart_get_rs485_mode(&pdev->dev, &port->rs485); + port->mapbase = mem_res->start; port->line = id; port->irq = irq_res->start; @@ -690,6 +778,7 @@ static int ar933x_uart_probe(struct platform_device *pdev) port->regshift = 2; port->fifosize = AR933X_UART_FIFO_SIZE; port->ops = &ar933x_uart_ops; + port->rs485_config = ar933x_config_rs485; baud = ar933x_uart_get_baud(port->uartclk, AR933X_UART_MAX_SCALE, 1); up->min_baud = max_t(unsigned int, baud, AR933X_UART_MIN_BAUD); @@ -697,6 +786,18 @@ static int ar933x_uart_probe(struct platform_device *pdev) baud = ar933x_uart_get_baud(port->uartclk, 0, AR933X_UART_MAX_STEP); up->max_baud = min_t(unsigned int, baud, AR933X_UART_MAX_BAUD); + up->gpios = mctrl_gpio_init(port, 0); + if (IS_ERR(up->gpios) && PTR_ERR(up->gpios) != -ENOSYS) + return PTR_ERR(up->gpios); + + up->rts_gpiod = mctrl_gpio_to_gpiod(up->gpios, UART_GPIO_RTS); + + if ((port->rs485.flags & SER_RS485_ENABLED) && + !up->rts_gpiod) { + dev_err(&pdev->dev, "lacking rts-gpio, disabling RS485\n"); + port->rs485.flags &= ~SER_RS485_ENABLED; + } + #ifdef CONFIG_SERIAL_AR933X_CONSOLE ar933x_console_ports[up->port.line] = up; #endif |