* [PATCH v2] serial: 8250_bcm2835aux: Add support for RTS/CTS auto flow control
@ 2026-09-30 12:56 Jan Čermák
2026-10-01 6:09 ` Jiri Slaby
0 siblings, 1 reply; 3+ messages in thread
From: Jan Čermák @ 2026-09-30 12:56 UTC (permalink / raw)
To: gregkh, jirislaby, florian.fainelli, rjui, sbranden
Cc: phil, stefan, bcm-kernel-feedback-list, linux-serial,
linux-rpi-kernel, linux-arm-kernel, linux-kernel,
Jan Čermák
The bcm2835aux mini UART can automatically de-assert RTS based on the RX
FIFO fill level and gate the transmitter on CTS, but the driver never
made use of it. Wire it up:
- Enable AUTORTS/AUTOCTS (with the RTS4 threshold and inverted, i.e.
conventional active-low, polarity) whenever CRTSCTS is set on ports
declaring the "uart-has-rtscts" property, advertised via
UPF_HARD_FLOW.
- Gate the AUTORTS function on TIOCM_RTS in set_mctrl() so that clearing
RTS (port close, TIOCMBIC, B0) actually de-asserts the pin while auto
flow control is active.
- Implement throttle()/unthrottle() by disabling the RX interrupts and
letting the FIFO fill up, making the hardware de-assert RTS - the same
approach as in 8250_omap. Re-sync this state on termios and rs485
changes, as those paths may re-enable the RX interrupts (or leave them
disabled) behind the throttle's back.
- Never enable auto flow control together with rs485, which uses RTS as
the transceiver direction control. As the software CTS fallback of the
serial core cannot work without a modem status interrupt, strip
CRTSCTS whenever it cannot be handled in hardware. Keep it when a CTS
GPIO is present, as the serial core's mctrl_gpio handling then
delivers the interrupt and the software fallback keeps working.
- Restore RXEN when rs485 is disabled, as the emulation may be torn down
without the rs485_stop_tx() callback ever being called, which would
leave the receiver disabled.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Jan Čermák <sairon@sairon.cz>
---
Notes:
v2:
- Keep CRTSCTS when a CTS GPIO is configured on the port, so the
serial core's GPIO-driven software CTS fallback keeps working
[Sashiko]
- Take a tty reference with tty_port_tty_get() in
bcm2835aux_tty_throttled() [Sashiko]
v1: https://lore.kernel.org/r/cover.1786030659.git.sairon@sairon.cz
drivers/tty/serial/8250/8250_bcm2835aux.c | 171 +++++++++++++++++++++-
1 file changed, 167 insertions(+), 4 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_bcm2835aux.c b/drivers/tty/serial/8250/8250_bcm2835aux.c
index 0609582a62f7..5b32448885ce 100644
--- a/drivers/tty/serial/8250/8250_bcm2835aux.c
+++ b/drivers/tty/serial/8250/8250_bcm2835aux.c
@@ -7,9 +7,9 @@
* Based on 8250_lpc18xx.c:
* Copyright (C) 2015 Joachim Eastwood <manabian@gmail.com>
*
- * The bcm2835aux is capable of RTS auto flow-control, but this driver doesn't
- * take advantage of it yet. When adding support, be sure not to enable it
- * simultaneously to rs485.
+ * The bcm2835aux's RTS/CTS auto flow-control is enabled for ports declaring
+ * the "uart-has-rtscts" property, but never simultaneously to rs485, which
+ * repurposes RTS as the transceiver direction control.
*/
#include <linux/clk.h>
@@ -19,6 +19,7 @@
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
+#include <linux/tty.h>
#include "8250.h"
@@ -34,6 +35,18 @@
#define BCM2835_AUX_UART_CNTL_RTSINV 0x40 /* Invert auto RTS polarity */
#define BCM2835_AUX_UART_CNTL_CTSINV 0x80 /* Invert auto CTS polarity */
+/*
+ * Set auto RTS to de-assert with 4 FIFO slots left (RTS4).
+ *
+ * The RTSINV/CTSINV bits select the auto flow assert level, which out of
+ * reset is active-high. Set them to get the conventional active-low RTS/CTS.
+ */
+#define BCM2835_AUX_UART_CNTL_AUTORTS_MASK \
+ (BCM2835_AUX_UART_CNTL_AUTORTS | BCM2835_AUX_UART_CNTL_RTS4 | \
+ BCM2835_AUX_UART_CNTL_RTSINV)
+#define BCM2835_AUX_UART_CNTL_AUTOCTS_MASK \
+ (BCM2835_AUX_UART_CNTL_AUTOCTS | BCM2835_AUX_UART_CNTL_CTSINV)
+
/**
* struct bcm2835aux_data - driver private data of BCM2835 auxiliary UART
* @clk: clock producer of the port's uartclk
@@ -46,6 +59,150 @@ struct bcm2835aux_data {
u32 cntl;
};
+static bool bcm2835aux_tty_throttled(struct uart_port *port)
+{
+ struct tty_struct *tty;
+ bool throttled;
+
+ if (!port->state)
+ return false;
+
+ tty = tty_port_tty_get(&port->state->port);
+ throttled = tty && tty_throttled(tty);
+ tty_kref_put(tty);
+
+ return throttled;
+}
+
+/* Program the CNTL auto flow bits from port->status and mctrl */
+static void bcm2835aux_update_flow(struct uart_port *port, unsigned int mctrl)
+{
+ struct bcm2835aux_data *data = dev_get_drvdata(port->dev);
+ struct uart_8250_port *up = up_to_u8250p(port);
+
+ data->cntl &= ~(BCM2835_AUX_UART_CNTL_AUTORTS_MASK |
+ BCM2835_AUX_UART_CNTL_AUTOCTS_MASK);
+ if ((port->status & UPSTAT_AUTORTS) && (mctrl & TIOCM_RTS))
+ data->cntl |= BCM2835_AUX_UART_CNTL_AUTORTS_MASK;
+ if (port->status & UPSTAT_AUTOCTS)
+ data->cntl |= BCM2835_AUX_UART_CNTL_AUTOCTS_MASK;
+
+ serial_out(up, BCM2835_AUX_UART_CNTL, data->cntl);
+}
+
+static void bcm2835aux_enable_rx_irq(struct uart_port *port)
+{
+ struct uart_8250_port *up = up_to_u8250p(port);
+
+ up->ier |= UART_IER_RLSI | UART_IER_RDI;
+ serial_out(up, UART_IER, up->ier);
+}
+
+static void bcm2835aux_set_autoflow(struct uart_port *port, tcflag_t cflag,
+ bool rs485_enabled)
+{
+ bool was_enabled = port->status & UPSTAT_AUTORTS;
+ bool enable;
+
+ /*
+ * rs485 uses RTS as the transceiver direction control, which is
+ * mutually exclusive with the auto flow-control of the pin.
+ */
+ enable = (cflag & CRTSCTS) && (port->flags & UPF_HARD_FLOW) &&
+ !rs485_enabled;
+
+ if (enable) {
+ port->status |= UPSTAT_AUTORTS | UPSTAT_AUTOCTS;
+ /* Restore the throttle state lost in startup/resume/rs485 paths */
+ if (bcm2835aux_tty_throttled(port)) {
+ /* let the FIFO fill up and de-assert RTS in hardware */
+ port->ops->stop_rx(port);
+ /* reclaim an RTS lowered by software flow control */
+ if ((cflag & CBAUD) != B0)
+ port->mctrl |= TIOCM_RTS;
+ }
+ } else {
+ port->status &= ~(UPSTAT_AUTORTS | UPSTAT_AUTOCTS);
+ /* unthrottle() will no longer be called after leaving auto-RTS mode */
+ if (was_enabled)
+ bcm2835aux_enable_rx_irq(port);
+ }
+
+ bcm2835aux_update_flow(port, port->mctrl);
+}
+
+static void bcm2835aux_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+ serial8250_do_set_mctrl(port, mctrl);
+ bcm2835aux_update_flow(port, mctrl);
+}
+
+static void bcm2835aux_set_termios(struct uart_port *port,
+ struct ktermios *termios,
+ const struct ktermios *old)
+{
+ struct uart_8250_port *up = up_to_u8250p(port);
+ bool hw_flow = (port->flags & UPF_HARD_FLOW) &&
+ !(port->rs485.flags & SER_RS485_ENABLED);
+
+ /*
+ * Strip CRTSCTS when the hardware auto flow-control cannot be
+ * used, as the serial core's software CTS fallback cannot work
+ * without a modem status interrupt. A CTS GPIO provides one.
+ */
+ if (!hw_flow && !mctrl_gpio_to_gpiod(up->gpios, UART_GPIO_CTS))
+ termios->c_cflag &= ~CRTSCTS;
+
+ serial8250_do_set_termios(port, termios, old);
+
+ guard(uart_port_lock_irqsave)(port);
+
+ bcm2835aux_set_autoflow(port, termios->c_cflag,
+ port->rs485.flags & SER_RS485_ENABLED);
+}
+
+static void bcm2835aux_throttle(struct uart_port *port)
+{
+ guard(uart_port_lock_irqsave)(port);
+
+ port->ops->stop_rx(port);
+}
+
+static void bcm2835aux_unthrottle(struct uart_port *port)
+{
+ guard(uart_port_lock_irqsave)(port);
+
+ bcm2835aux_enable_rx_irq(port);
+}
+
+static int bcm2835aux_rs485_config(struct uart_port *port,
+ struct ktermios *termios,
+ struct serial_rs485 *rs485)
+{
+ tcflag_t cflag = termios ? termios->c_cflag : 0;
+ bool rs485_enabled;
+ int ret;
+
+ ret = serial8250_em485_config(port, termios, rs485);
+ if (ret)
+ return ret;
+
+ rs485_enabled = rs485->flags & SER_RS485_ENABLED;
+ if (!rs485_enabled) {
+ struct bcm2835aux_data *data = dev_get_drvdata(port->dev);
+
+ data->cntl |= BCM2835_AUX_UART_CNTL_RXEN;
+ }
+
+ /*
+ * termios is NULL when rs485 is set up from firmware properties at
+ * port registration, before the port has been opened.
+ */
+ bcm2835aux_set_autoflow(port, cflag, rs485_enabled);
+
+ return 0;
+}
+
static void bcm2835aux_rs485_start_tx(struct uart_8250_port *up, bool toggle_ier)
{
if (!(up->port.rs485.flags & SER_RS485_RX_DURING_TX)) {
@@ -99,7 +256,11 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev)
up.port.dev = &pdev->dev;
up.port.type = PORT_16550;
up.port.flags = UPF_FIXED_PORT | UPF_FIXED_TYPE | UPF_SKIP_TEST | UPF_IOREMAP;
- up.port.rs485_config = serial8250_em485_config;
+ up.port.set_termios = bcm2835aux_set_termios;
+ up.port.set_mctrl = bcm2835aux_set_mctrl;
+ up.port.throttle = bcm2835aux_throttle;
+ up.port.unthrottle = bcm2835aux_unthrottle;
+ up.port.rs485_config = bcm2835aux_rs485_config;
up.port.rs485_supported = serial8250_em485_supported;
up.rs485_start_tx = bcm2835aux_rs485_start_tx;
up.rs485_stop_tx = bcm2835aux_rs485_stop_tx;
@@ -134,6 +295,8 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev)
ret = uart_read_port_properties(&up.port);
if (ret)
goto rm_swnode;
+ if (device_property_read_bool(&pdev->dev, "uart-has-rtscts"))
+ up.port.flags |= UPF_HARD_FLOW;
up.port.regshift = 2;
up.port.fifosize = 8;
base-commit: 551c722f40809618230001baccf219193e22fc5a
--
2.55.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v2] serial: 8250_bcm2835aux: Add support for RTS/CTS auto flow control
2026-09-30 12:56 [PATCH v2] serial: 8250_bcm2835aux: Add support for RTS/CTS auto flow control Jan Čermák
@ 2026-10-01 6:09 ` Jiri Slaby
2026-10-01 8:39 ` Greg KH
0 siblings, 1 reply; 3+ messages in thread
From: Jiri Slaby @ 2026-10-01 6:09 UTC (permalink / raw)
To: Jan Čermák, gregkh, florian.fainelli, rjui, sbranden
Cc: phil, stefan, bcm-kernel-feedback-list, linux-serial,
linux-rpi-kernel, linux-arm-kernel, linux-kernel
On 30. 09. 26, 14:56, Jan Čermák wrote:
> --- a/drivers/tty/serial/8250/8250_bcm2835aux.c
> +++ b/drivers/tty/serial/8250/8250_bcm2835aux.c
...
> @@ -46,6 +59,150 @@ struct bcm2835aux_data {
> u32 cntl;
> };
>
> +static bool bcm2835aux_tty_throttled(struct uart_port *port)
> +{
> + struct tty_struct *tty;
> + bool throttled;
> +
> + if (!port->state)
> + return false;
> +
> + tty = tty_port_tty_get(&port->state->port);
> + throttled = tty && tty_throttled(tty);
> + tty_kref_put(tty);
> +
> + return throttled;
Instead:
scoped_guard(tty_port_tty, port)
return tty_throttled(scoped_tty());
return false;
> +}
thanks,
--
js
suse labs
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v2] serial: 8250_bcm2835aux: Add support for RTS/CTS auto flow control
2026-10-01 6:09 ` Jiri Slaby
@ 2026-10-01 8:39 ` Greg KH
0 siblings, 0 replies; 3+ messages in thread
From: Greg KH @ 2026-10-01 8:39 UTC (permalink / raw)
To: Jiri Slaby
Cc: Jan Čermák, florian.fainelli, rjui, sbranden, phil,
stefan, bcm-kernel-feedback-list, linux-serial, linux-rpi-kernel,
linux-arm-kernel, linux-kernel
On Thu, Oct 01, 2026 at 08:09:32AM +0200, Jiri Slaby wrote:
> On 30. 09. 26, 14:56, Jan Čermák wrote:
> > --- a/drivers/tty/serial/8250/8250_bcm2835aux.c
> > +++ b/drivers/tty/serial/8250/8250_bcm2835aux.c
> ...
> > @@ -46,6 +59,150 @@ struct bcm2835aux_data {
> > u32 cntl;
> > };
> > +static bool bcm2835aux_tty_throttled(struct uart_port *port)
> > +{
> > + struct tty_struct *tty;
> > + bool throttled;
> > +
> > + if (!port->state)
> > + return false;
> > +
> > + tty = tty_port_tty_get(&port->state->port);
> > + throttled = tty && tty_throttled(tty);
> > + tty_kref_put(tty);
> > +
> > + return throttled;
>
> Instead:
>
> scoped_guard(tty_port_tty, port)
> return tty_throttled(scoped_tty());
>
> return false;
LLMs do not understand "modern" kernel programming styles :(
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-01 8:39 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-30 12:56 [PATCH v2] serial: 8250_bcm2835aux: Add support for RTS/CTS auto flow control Jan Čermák
2026-10-01 6:09 ` Jiri Slaby
2026-10-01 8:39 ` Greg KH
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®