MOXA PCIe boards have 4 serial interfaces and don't require additional stuff to switch between interfaces: - RS232 - RS422 - RS485_2W (half-duplex) - RS485_4W (full-duplex) By using ioctl command "TIOCRS485", it can switch between default interface and RS485 if supported. That means, for RS422/RS485 board, it can switch between RS422 and RS485 by setting the flags within struct serial_rs485. However, for the RS232/RS422/RS485 board, it can only switch between RS232 and RS485, there's no flag for switching interface into RS422. This patch adds a flag call "SER_RS422_ENALBED" in serial.h and modifies serial_core.c to make it possible to switch interface between RS232, RS422 and RS485. Signed-off-by: Crescent CY Hsieh <crescentcy.hsieh@xxxxxxxx> --- drivers/tty/serial/8250/8250_pci.c | 44 ++++++++++++++++++++++++++++++ drivers/tty/serial/serial_core.c | 21 ++++++++++++-- include/uapi/linux/serial.h | 4 +++ 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c index 66a2450da..6eabf6bf5 100644 --- a/drivers/tty/serial/8250/8250_pci.c +++ b/drivers/tty/serial/8250/8250_pci.c @@ -1983,6 +1983,10 @@ enum { MOXA_SUPP_RS485 = BIT(2), }; +static const struct serial_rs485 pci_moxa_rs485_supported = { + .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | SER_RS485_RX_DURING_TX | SER_RS422_ENABLED, +}; + static bool pci_moxa_is_mini_pcie(unsigned short device) { if (device == PCI_DEVICE_ID_MOXA_CP102N || @@ -2032,6 +2036,37 @@ static int pci_moxa_set_interface(const struct pci_dev *dev, return 0; } +/* + * MOXA PCIe boards support switching the serial interface using the ioctl + * command "TIOCSRS485". + * + * RS232 = (no flags are set) + * RS422 = SER_RS422_ENABLED + * RS485_2W (half-duplex) = SER_RS485_ENABLED + * RS485_4W (full-duplex) = SER_RS485_ENABLED | SER_RS485_RX_DURING_TX + */ +static int pci_moxa_rs485_config(struct uart_port *port, + struct ktermios *termios, + struct serial_rs485 *rs485) +{ + struct pci_dev *dev = to_pci_dev(port->dev); + u8 mode = MOXA_RS232; + + if (rs485->flags & SER_RS485_ENABLED) { + if (rs485->flags & SER_RS485_RX_DURING_TX) + mode = MOXA_RS485_4W; + else + mode = MOXA_RS485_2W; + } else if (rs485->flags & SER_RS422_ENABLED) { + mode = MOXA_RS422; + } else { + if (!(pci_moxa_supported_rs(dev) & MOXA_SUPP_RS232)) + return -ENODEV; + } + + return pci_moxa_set_interface(dev, port->port_id, mode); +} + static int pci_moxa_init(struct pci_dev *dev) { unsigned short device = dev->device; @@ -2067,9 +2102,18 @@ pci_moxa_setup(struct serial_private *priv, const struct pciserial_board *board, struct uart_8250_port *port, int idx) { + struct pci_dev *dev = priv->dev; unsigned int bar = FL_GET_BASE(board->flags); int offset; + if (pci_moxa_supported_rs(dev) & MOXA_SUPP_RS485) { + port->port.rs485_config = pci_moxa_rs485_config; + port->port.rs485_supported = pci_moxa_rs485_supported; + + if (!(pci_moxa_supported_rs(dev) & MOXA_SUPP_RS232)) + port->port.rs485.flags = SER_RS422_ENABLED; + } + if (board->num_ports == 4 && idx == 3) offset = 7 * board->uart_offset; else diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 831d03361..e4ea0e428 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -1305,7 +1305,7 @@ static int uart_get_icount(struct tty_struct *tty, #define SER_RS485_LEGACY_FLAGS (SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | \ SER_RS485_RTS_AFTER_SEND | SER_RS485_RX_DURING_TX | \ - SER_RS485_TERMINATE_BUS) + SER_RS485_TERMINATE_BUS | SER_RS422_ENABLED) static int uart_check_rs485_flags(struct uart_port *port, struct serial_rs485 *rs485) { @@ -1371,11 +1371,26 @@ static void uart_sanitize_serial_rs485(struct uart_port *port, struct serial_rs4 { u32 supported_flags = port->rs485_supported.flags; - if (!(rs485->flags & SER_RS485_ENABLED)) { + if (!(rs485->flags & SER_RS485_ENABLED) && !(rs485->flags & SER_RS422_ENABLED)) { memset(rs485, 0, sizeof(*rs485)); return; } + /* Pick sane setting if the user enables both interfaces */ + if (rs485->flags & SER_RS485_ENABLED && rs485->flags & SER_RS422_ENABLED) { + dev_warn_ratelimited(port->dev, + "%s (%d): Invalid serial interface setting, using RS485 instead\n", + port->name, port->line); + rs485->flags &= ~(SER_RS422_ENABLED); + } + + /* Clear other bits and return if enable RS422 */ + if (rs485->flags & SER_RS422_ENABLED) { + memset(rs485, 0, sizeof(*rs485)); + rs485->flags |= SER_RS422_ENABLED; + return; + } + /* Pick sane settings if the user hasn't */ if ((supported_flags & (SER_RS485_RTS_ON_SEND|SER_RS485_RTS_AFTER_SEND)) && !(rs485->flags & SER_RS485_RTS_ON_SEND) == @@ -1400,7 +1415,7 @@ static void uart_sanitize_serial_rs485(struct uart_port *port, struct serial_rs4 static void uart_set_rs485_termination(struct uart_port *port, const struct serial_rs485 *rs485) { - if (!(rs485->flags & SER_RS485_ENABLED)) + if (!(rs485->flags & SER_RS485_ENABLED) && !(rs485->flags & SER_RS422_ENABLED)) return; gpiod_set_value_cansleep(port->rs485_term_gpio, diff --git a/include/uapi/linux/serial.h b/include/uapi/linux/serial.h index 53bc1af67..427609fd5 100644 --- a/include/uapi/linux/serial.h +++ b/include/uapi/linux/serial.h @@ -137,6 +137,8 @@ struct serial_icounter_struct { * * %SER_RS485_ADDRB - Enable RS485 addressing mode. * * %SER_RS485_ADDR_RECV - Receive address filter (enables @addr_recv). Requires %SER_RS485_ADDRB. * * %SER_RS485_ADDR_DEST - Destination address (enables @addr_dest). Requires %SER_RS485_ADDRB. + * + * * %SER_RS422_ENABLED - RS422 enabled. */ struct serial_rs485 { __u32 flags; @@ -149,6 +151,8 @@ struct serial_rs485 { #define SER_RS485_ADDR_RECV (1 << 7) #define SER_RS485_ADDR_DEST (1 << 8) +#define SER_RS422_ENABLED (1 << 9) + __u32 delay_rts_before_send; __u32 delay_rts_after_send; -- 2.34.1