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 | 41 ++++++++++++++++++++++++++++++ drivers/tty/serial/serial_core.c | 20 ++++++++++++--- include/uapi/linux/serial.h | 4 +++ 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c index 31bfe5381000..86dce37f08c0 100644 --- a/drivers/tty/serial/8250/8250_pci.c +++ b/drivers/tty/serial/8250/8250_pci.c @@ -1898,6 +1898,10 @@ pci_sunix_setup(struct serial_private *priv, #define MOXA_GPIO_SET_ALL_OUTPUT 0x0F +static const struct serial_rs485 pci_moxa_rs485_supported = { + .flags = SER_RS485_ENABLED | SER_RS485_RX_DURING_TX | SER_RS422_ENABLED, +}; + static int pci_moxa_set_interface(struct pci_dev *dev, unsigned int port_idx, unsigned char mode) @@ -1921,6 +1925,30 @@ static int pci_moxa_set_interface(struct pci_dev *dev, return 0; } +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); + unsigned short device = dev->device; + unsigned char 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 ((device & 0x0F00) == 0x0300) { + pci_warn(dev, "RS232 interface is not supported."); + return -EINVAL; + } + } + return pci_moxa_set_interface(dev, port->line, mode); +} + static int pci_moxa_init(struct pci_dev *dev) { unsigned short device = dev->device; @@ -1965,9 +1993,22 @@ static int pci_moxa_setup(struct serial_private *priv, struct uart_8250_port *port, int idx) { + struct pci_dev *dev = priv->dev; + unsigned short device = dev->device; unsigned int bar = FL_GET_BASE(board->flags); int offset; + /* + * For the device IDs of MOXA PCIe boards match the pattern 0x*3** and 0x*1**, + * these boards support switching interface between RS422/RS485 using TIOCSRS485. + */ + if ((device & 0x0F00) == 0x0100 || (device & 0x0F00) == 0x0300) { + port->port.rs485_config = pci_moxa_rs485_config; + port->port.rs485_supported = pci_moxa_rs485_supported; + + if ((device & 0x0F00) == 0x0300) + port->port.rs485.flags = SER_RE422_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 7bdc21d5e13b..f316d9705809 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -1299,7 +1299,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_ENALBED) static int uart_check_rs485_flags(struct uart_port *port, struct serial_rs485 *rs485) { @@ -1365,11 +1365,23 @@ 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 enalbe 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) == @@ -1394,7 +1406,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 53bc1af67a41..427609fd52cb 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