* [PATCH 0/2] serial: core: add transceiver interface select GPIO
@ 2026-10-21 5:19 Tapio Reijonen
2026-10-21 5:19 ` [PATCH 1/2] dt-bindings: serial: rs485: Add " Tapio Reijonen
2026-10-21 5:19 ` [PATCH 2/2] serial: core: Drive the " Tapio Reijonen
0 siblings, 2 replies; 4+ messages in thread
From: Tapio Reijonen @ 2026-10-21 5:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-serial, devicetree, linux-kernel, Lukas Wunner,
Christoph Niedermaier, Ilpo Järvinen, Lino Sanfilippo,
Hugo Villeneuve, Tapio Reijonen
Multiprotocol transceivers such as the LTC2873 provide an RS485
interface and an alternative interface, typically RS232, on the same
bus pins and choose between them with an interface select input. Add
an optional rs485-select-gpios property next to the existing
rs485-term-gpios and rs485-rx-during-tx-gpios, and let the serial core
drive it from the RS485 configuration state, so that every UART driver
using uart_get_rs485_mode() supports such transceivers without driver
changes.
Tested on an i.MX6SX UART and on a MAX14830 channel, each wired to an
LTC2873, by observing the bus line while switching RS485 on and off
with TIOCSRS485.
Signed-off-by: Tapio Reijonen <tapio.reijonen@vaisala.com>
---
Tapio Reijonen (2):
dt-bindings: serial: rs485: Add transceiver interface select GPIO
serial: core: Drive the transceiver interface select GPIO
.../devicetree/bindings/serial/rs485.yaml | 8 ++++++
drivers/tty/serial/serial_core.c | 29 +++++++++++++++++++++-
include/linux/serial_core.h | 1 +
3 files changed, 37 insertions(+), 1 deletion(-)
---
base-commit: 1184f87e2790e8af9be3116f608d2e86b4792d7d
change-id: 20261019-serial-rs485-mode-gpio-e76bb72b6f21
Best regards,
--
Tapio Reijonen <tapio.reijonen@vaisala.com>
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 1/2] dt-bindings: serial: rs485: Add transceiver interface select GPIO
2026-10-21 5:19 [PATCH 0/2] serial: core: add transceiver interface select GPIO Tapio Reijonen
@ 2026-10-21 5:19 ` Tapio Reijonen
2026-10-21 5:19 ` [PATCH 2/2] serial: core: Drive the " Tapio Reijonen
1 sibling, 0 replies; 4+ messages in thread
From: Tapio Reijonen @ 2026-10-21 5:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-serial, devicetree, linux-kernel, Lukas Wunner,
Christoph Niedermaier, Ilpo Järvinen, Lino Sanfilippo,
Hugo Villeneuve, Tapio Reijonen
Multiprotocol transceivers such as the LTC2873 provide both an RS232
and an RS485 interface on the same bus pins and choose between them
with an interface select input: on the LTC2873 a logic low on the
485/232 pin enables RS232 mode and a high enables RS485 mode.
Add an optional rs485-select-gpios property for that input, next to
the existing rs485-term-gpios and rs485-rx-during-tx-gpios. The active
state selects the RS485 interface, the inactive state selects the
transceiver's alternative interface, RS232 on the LTC2873.
Assisted-by: LLM
Signed-off-by: Tapio Reijonen <tapio.reijonen@vaisala.com>
---
Documentation/devicetree/bindings/serial/rs485.yaml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Documentation/devicetree/bindings/serial/rs485.yaml b/Documentation/devicetree/bindings/serial/rs485.yaml
index b93254ad2a287ad496f791e3ee90eb9defa0609d..2b5fd405e164f308b6036331edce7eb61da2f3ff 100644
--- a/Documentation/devicetree/bindings/serial/rs485.yaml
+++ b/Documentation/devicetree/bindings/serial/rs485.yaml
@@ -60,6 +60,14 @@ properties:
the active state enables RX during TX.
maxItems: 1
+ rs485-select-gpios:
+ description: GPIO connected to the interface select input of a
+ multiprotocol transceiver that provides an RS485 interface and an
+ alternative interface, typically RS232, on the same bus pins. The
+ active state selects the RS485 interface, the inactive state selects
+ the alternative interface.
+ maxItems: 1
+
additionalProperties: true
...
--
2.47.3
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 2/2] serial: core: Drive the transceiver interface select GPIO
2026-10-21 5:19 [PATCH 0/2] serial: core: add transceiver interface select GPIO Tapio Reijonen
2026-10-21 5:19 ` [PATCH 1/2] dt-bindings: serial: rs485: Add " Tapio Reijonen
@ 2026-10-21 5:19 ` Tapio Reijonen
2026-10-21 5:41 ` sashiko-bot
1 sibling, 1 reply; 4+ messages in thread
From: Tapio Reijonen @ 2026-10-21 5:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-serial, devicetree, linux-kernel, Lukas Wunner,
Christoph Niedermaier, Ilpo Järvinen, Lino Sanfilippo,
Hugo Villeneuve, Tapio Reijonen
Multiprotocol transceivers such as the LTC2873 provide both an RS232
and an RS485 interface on the same bus pins and choose between them
with an interface select input. There is no generic way to drive that
input today, so a port wired to such a transceiver is stuck in
whichever interface the pin is strapped to.
Add the optional rs485-select-gpios property to the GPIOs acquired by
uart_get_rs485_mode() and drive it from the RS485 configuration state:
the GPIO is active while RS485 is enabled, which selects the RS485
interface, and inactive otherwise, which selects the alternative
interface, typically RS232. RS422 is a mode of SER_RS485_ENABLED and
therefore selects the RS485 interface as well.
The GPIO is requested with an initial state derived from
linux,rs485-enabled-at-boot-time, so the transceiver presents the right
interface from probe on. The state is applied in uart_rs485_config() for
the boot-time and resume paths and in uart_set_rs485_config() for the
ioctl path. Unlike the termination and RX-during-TX GPIOs, which only
matter while RS485 is enabled, this GPIO must also be driven when RS485
is disabled to return the transceiver to its alternative interface. It
is deasserted when the driver rejects the boot-time configuration and
restored to the previous setting when the driver rejects an ioctl, as is
done for the other GPIOs.
In RS485 mode RTS is the driver enable of the transceiver. The GPIO is
therefore driven after the driver callback when RS485 is enabled, so
RTS already sits at its RS485 idle level when the transceiver switches
over, and before the driver callback when RS485 is disabled, so the
transceiver has left RS485 mode before RTS reverts to modem control.
Drivers calling uart_get_rs485_mode() need no change to support such
transceivers.
Assisted-by: LLM
Signed-off-by: Tapio Reijonen <tapio.reijonen@vaisala.com>
---
drivers/tty/serial/serial_core.c | 29 ++++++++++++++++++++++++++++-
include/linux/serial_core.h | 1 +
2 files changed, 29 insertions(+), 1 deletion(-)
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index c0deec0e6af41db10de15ccca4169c603d9cebf6..57b022d0cf01b00d0d383d833d13afcfa6084760 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -1435,13 +1435,22 @@ static void uart_set_rs485_rx_during_tx(struct uart_port *port,
!!(rs485->flags & SER_RS485_RX_DURING_TX));
}
+static void uart_set_rs485_select_gpio(struct uart_port *port,
+ const struct serial_rs485 *rs485)
+{
+ gpiod_set_value_cansleep(port->rs485_select_gpio,
+ !!(rs485->flags & SER_RS485_ENABLED));
+}
+
static int uart_rs485_config(struct uart_port *port)
{
struct serial_rs485 *rs485 = &port->rs485;
int ret;
- if (!(rs485->flags & SER_RS485_ENABLED))
+ if (!(rs485->flags & SER_RS485_ENABLED)) {
+ uart_set_rs485_select_gpio(port, rs485);
return 0;
+ }
uart_sanitize_serial_rs485(port, rs485);
uart_set_rs485_termination(port, rs485);
@@ -1456,6 +1465,8 @@ static int uart_rs485_config(struct uart_port *port)
gpiod_set_value_cansleep(port->rs485_rx_during_tx_gpio, 0);
}
+ uart_set_rs485_select_gpio(port, rs485);
+
return ret;
}
@@ -1492,6 +1503,10 @@ static int uart_set_rs485_config(struct tty_struct *tty, struct uart_port *port,
uart_set_rs485_termination(port, &rs485);
uart_set_rs485_rx_during_tx(port, &rs485);
+ /* Leave RS485 before RTS reverts to modem control below */
+ if (!(rs485.flags & SER_RS485_ENABLED))
+ uart_set_rs485_select_gpio(port, &rs485);
+
scoped_guard(uart_port_lock_irqsave, port) {
ret = port->rs485_config(port, &tty->termios, &rs485);
if (!ret) {
@@ -1508,9 +1523,14 @@ static int uart_set_rs485_config(struct tty_struct *tty, struct uart_port *port,
!!(port->rs485.flags & SER_RS485_TERMINATE_BUS));
gpiod_set_value_cansleep(port->rs485_rx_during_tx_gpio,
!!(port->rs485.flags & SER_RS485_RX_DURING_TX));
+ uart_set_rs485_select_gpio(port, &port->rs485);
return ret;
}
+ /* Enter RS485 only once the driver has parked RTS */
+ if (rs485.flags & SER_RS485_ENABLED)
+ uart_set_rs485_select_gpio(port, &rs485);
+
if (copy_to_user(rs485_user, &port->rs485, sizeof(port->rs485)))
return -EFAULT;
@@ -3643,6 +3663,13 @@ int uart_get_rs485_mode(struct uart_port *port)
if (port->rs485_rx_during_tx_gpio)
port->rs485_supported.flags |= SER_RS485_RX_DURING_TX;
+ dflags = (rs485conf->flags & SER_RS485_ENABLED) ?
+ GPIOD_OUT_HIGH : GPIOD_OUT_LOW;
+ desc = devm_gpiod_get_optional(dev, "rs485-select", dflags);
+ if (IS_ERR(desc))
+ return dev_err_probe(dev, PTR_ERR(desc), "Cannot get rs485-select-gpios\n");
+ port->rs485_select_gpio = desc;
+
return 0;
}
EXPORT_SYMBOL_GPL(uart_get_rs485_mode);
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index 4eeb8253752aa08b27829e80a259c7db65728e8e..fe90a8aada98cacf5a80f20ec2a91e249c92d0ed 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -600,6 +600,7 @@ struct uart_port {
struct serial_rs485 rs485_supported; /* Supported mask for serial_rs485 */
struct gpio_desc *rs485_term_gpio; /* enable RS485 bus termination */
struct gpio_desc *rs485_rx_during_tx_gpio; /* Output GPIO that sets the state of RS485 RX during TX */
+ struct gpio_desc *rs485_select_gpio; /* select the RS485 interface */
struct serial_iso7816 iso7816;
void *private_data; /* generic platform data pointer */
};
--
2.47.3
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH 2/2] serial: core: Drive the transceiver interface select GPIO
2026-10-21 5:19 ` [PATCH 2/2] serial: core: Drive the " Tapio Reijonen
@ 2026-10-21 5:41 ` sashiko-bot
0 siblings, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-10-21 5:41 UTC (permalink / raw)
To: Tapio Reijonen; +Cc: linux-serial, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] serial: core: stale rs485_select_gpio access in uart_rs485_config()
- [Medium] serial: core: rs485-select asserted before RTS parked in uart_get_rs485_mode()
--
commit 49736223bf33ff5e8eb201ccbef821265b8748f6
Author: Tapio Reijonen <tapio.reijonen@vaisala.com>
serial: core: Drive the transceiver interface select GPIO
This commit adds support for an optional rs485-select-gpios property in
uart_get_rs485_mode() to switch multiprotocol transceivers between RS485
and alternative interfaces such as RS232. The GPIO state is updated during
boot/resume configuration in uart_rs485_config() and during ioctl handling
in uart_set_rs485_config().
> diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
> index c0deec0e6af41..57b022d0cf01b 100644
> --- a/drivers/tty/serial/serial_core.c
> +++ b/drivers/tty/serial/serial_core.c
> @@ -1435,13 +1435,22 @@ static void uart_set_rs485_rx_during_tx(struct uart_port *port,
> !!(rs485->flags & SER_RS485_RX_DURING_TX));
> }
>
> +static void uart_set_rs485_select_gpio(struct uart_port *port,
> + const struct serial_rs485 *rs485)
> +{
> + gpiod_set_value_cansleep(port->rs485_select_gpio,
> + !!(rs485->flags & SER_RS485_ENABLED));
> +}
> +
> static int uart_rs485_config(struct uart_port *port)
> {
> struct serial_rs485 *rs485 = &port->rs485;
> int ret;
>
> - if (!(rs485->flags & SER_RS485_ENABLED))
> + if (!(rs485->flags & SER_RS485_ENABLED)) {
> + uart_set_rs485_select_gpio(port, rs485);
> return 0;
> + }
[Severity: High]
Could calling uart_set_rs485_select_gpio() unconditionally here when
SER_RS485_ENABLED is not set access a released port->rs485_select_gpio
descriptor when a static uart_port slot is reused?
In drivers/tty/serial/8250/8250_core.c, __serial8250_register_8250_port()
reuses static entries from serial8250_ports[] and populates port->rs485_select_gpio
via uart_get_rs485_mode():
drivers/tty/serial/8250/8250_core.c:__serial8250_register_8250_port() {
...
uart->port.rs485_config = up->port.rs485_config;
uart->port.rs485_supported = up->port.rs485_supported;
uart->port.rs485 = up->port.rs485;
...
if (up->port.dev) {
uart->port.dev = up->port.dev;
ret = uart_get_rs485_mode(&uart->port);
if (ret)
return ret;
}
Because port->rs485_select_gpio is acquired with devm_gpiod_get_optional(),
its lifetime is bound to port->dev. If registration subsequently fails in
__serial8250_register_8250_port() (for example in mctrl_gpio_init() or
uart_add_one_port()) or the device is later unbound and unregistered via
serial8250_unregister_port(), devres releases the descriptor while leaving
the pointer set in the static serial8250_ports[] entry.
When a subsequent 8250 device claims the same slot without RS485 support or
without a firmware node, uart_get_rs485_mode() returns early without
resetting port->rs485_select_gpio:
drivers/tty/serial/serial_core.c:uart_get_rs485_mode() {
...
if (!(port->rs485_supported.flags & SER_RS485_ENABLED))
return 0;
if (!dev_fwnode(dev))
return 0;
Previously, uart_rs485_config() returned 0 immediately when
SER_RS485_ENABLED was clear. With this change, uart_configure_port() ->
uart_rs485_config() and uart_resume_port() -> uart_rs485_config() call
gpiod_set_value_cansleep(port->rs485_select_gpio, 0) using the dangling
pointer.
Should uart_get_rs485_mode() clear the RS485 GPIO pointers before its early
returns (or when a port is unregistered), and should uart_rs485_config()
also check port->rs485_supported.flags?
[ ... ]
> @@ -3643,6 +3663,13 @@ int uart_get_rs485_mode(struct uart_port *port)
> if (port->rs485_rx_during_tx_gpio)
> port->rs485_supported.flags |= SER_RS485_RX_DURING_TX;
>
> + dflags = (rs485conf->flags & SER_RS485_ENABLED) ?
> + GPIOD_OUT_HIGH : GPIOD_OUT_LOW;
> + desc = devm_gpiod_get_optional(dev, "rs485-select", dflags);
> + if (IS_ERR(desc))
> + return dev_err_probe(dev, PTR_ERR(desc), "Cannot get rs485-select-gpios\n");
> + port->rs485_select_gpio = desc;
> +
> return 0;
> }
> EXPORT_SYMBOL_GPL(uart_get_rs485_mode);
[Severity: Medium]
When linux,rs485-enabled-at-boot-time is set, doesn't requesting
rs485-select with GPIOD_OUT_HIGH in uart_get_rs485_mode() switch the
transceiver into RS485 mode before the UART driver has parked the RTS line
at its RS485 idle level?
During probe (for example in __serial8250_register_8250_port(),
imx_uart_probe(), serial_omap_probe_rs485(), and ar933x_uart_probe()),
uart_get_rs485_mode() runs before uart_add_one_port() ->
uart_configure_port() -> uart_rs485_config() invokes port->rs485_config()
to configure RTS and validate the boot-time settings.
In drivers/tty/serial/8250/8250_core.c, uart_get_rs485_mode() also runs
before mctrl_gpio_init() requests the RTS GPIO (which initializes it to
GPIOD_OUT_LOW regardless of rs485-rts-active-low) and before 8250
autoconfig manipulates UART_MCR_RTS:
drivers/tty/serial/8250/8250_core.c:__serial8250_register_8250_port() {
...
if (up->port.dev) {
uart->port.dev = up->port.dev;
ret = uart_get_rs485_mode(&uart->port);
if (ret)
return ret;
}
...
if (uart->port.dev && !has_acpi_companion(uart->port.dev)) {
struct mctrl_gpios *gpios = mctrl_gpio_init(&uart->port, 0);
Since RTS acts as the transceiver driver enable in RS485 mode, could
asserting rs485-select-gpios here in uart_get_rs485_mode() cause transient
bus contention on probe before uart_rs485_config() runs?
Should uart_get_rs485_mode() instead request rs485-select with
GPIOD_OUT_LOW and leave asserting it to uart_rs485_config() after
port->rs485_config() has parked RTS?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261021-serial-rs485-mode-gpio-v1-0-80d7b0903d93@vaisala.com?part=2
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-21 5:41 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-21 5:19 [PATCH 0/2] serial: core: add transceiver interface select GPIO Tapio Reijonen
2026-10-21 5:19 ` [PATCH 1/2] dt-bindings: serial: rs485: Add " Tapio Reijonen
2026-10-21 5:19 ` [PATCH 2/2] serial: core: Drive the " Tapio Reijonen
2026-10-21 5:41 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox