* [PATCH 0/1] Implement BCM2835 AUX RTS/CTS auto flow control
@ 2026-08-06 15:42 Jan Čermák
2026-08-06 15:42 ` [PATCH 1/1] serial: 8250_bcm2835aux: Add support for " Jan Čermák
0 siblings, 1 reply; 2+ messages in thread
From: Jan Čermák @ 2026-08-06 15:42 UTC (permalink / raw)
To: gregkh, jirislaby, florian.fainelli, rjui, sbranden
Cc: Jan Čermák, phil, stefan, bcm-kernel-feedback-list,
linux-serial, linux-rpi-kernel, linux-arm-kernel, linux-kernel
Hi everyone,
this is my attempt at adding hardware flow control to the BCM2835 AUX UART
driver. The hardware supports it and the driver source has always noted
that, but it was never implemented.
The driving factor for this contribution was that this peripheral is used
on Raspberry Pi as the alternative muxable UART for the BCM43455 Bluetooth
chip, should the user decide to use the "full" PL011 UART on the pin
header. But this always led to some compromises, as the mini UART wasn't as
robust as PL011, mainly because of the missing flow control. We use it for
Home Assistant Yellow, which is essentially a carrier board for CM4/CM5,
and with the recent update of the (downstream) Linux kernel to 6.18, the
Bluetooth started to be significantly flaky and failing to initialize on
boot [1]. Diagnostics showed that these failures are caused by RX buffer
overruns during the initialization, which were probably always present, but
some other changes apparently added up to greater instability of the
driver, leading to overrun errors in approximately 40% of boots when using
the mini UART for the Bluetooth link at 230400 baud.
I've originally tested this patch applied in the downstream rpi-6.18.y
branch, using the mini UART for Bluetooth, which improved stability of the
Bluetooth initialization greatly and allowed clean initialization even when
bumping the baud rate to 1.5M. Then I applied this patch to latest mainline
and did a couple of simulations forcing the UART to throttle by filling the
LDISC buffer, with an FTDI adapter connected on the other side of the line.
Note that for this to come into effect, the uart-has-rtscts property needs
to be present in the DTS. No in-tree device tree sets it on uart1 - the
property only appears on the PL011 nodes (uart0 on bcm2711, uarta on
bcm2712) used for the Bluetooth link. Raspberry Pi has the CTS and RTS
lines hooked up and muxable though, so it can be easily enabled when
wanted.
At this point, the implementation seems to be fully working in my tests,
but there may be some subtle issues I don't see, as I'm not fully familiar
with the subsystem. I used an LLM extensively during the implementation (as
I properly disclose with the Assisted-by tag) though at this point I hope
the patch is far from a one-shot vibe-coded mess, but please review
carefully.
Regards,
Jan
[1] https://github.com/home-assistant/operating-system/issues/4898
Jan Čermák (1):
serial: 8250_bcm2835aux: Add support for RTS/CTS auto flow control
drivers/tty/serial/8250/8250_bcm2835aux.c | 160 +++++++++++++++++++++-
1 file changed, 156 insertions(+), 4 deletions(-)
base-commit: 0d839570765118029aa8bf4a95444c6a11aacf85
--
2.55.0
^ permalink raw reply [flat|nested] 2+ messages in thread
* [PATCH 1/1] serial: 8250_bcm2835aux: Add support for RTS/CTS auto flow control
2026-08-06 15:42 [PATCH 0/1] Implement BCM2835 AUX RTS/CTS auto flow control Jan Čermák
@ 2026-08-06 15:42 ` Jan Čermák
0 siblings, 0 replies; 2+ messages in thread
From: Jan Čermák @ 2026-08-06 15:42 UTC (permalink / raw)
To: gregkh, jirislaby, florian.fainelli, rjui, sbranden
Cc: Jan Čermák, phil, stefan, bcm-kernel-feedback-list,
linux-serial, linux-rpi-kernel, linux-arm-kernel, linux-kernel
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.
- 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>
---
drivers/tty/serial/8250/8250_bcm2835aux.c | 160 +++++++++++++++++++++-
1 file changed, 156 insertions(+), 4 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_bcm2835aux.c b/drivers/tty/serial/8250/8250_bcm2835aux.c
index 0609582a62f7..cf9cf08ddb17 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,139 @@ struct bcm2835aux_data {
u32 cntl;
};
+static bool bcm2835aux_tty_throttled(struct uart_port *port)
+{
+ struct tty_struct *tty = port->state ? port->state->port.tty : NULL;
+
+ return tty && tty_throttled(tty);
+}
+
+/* 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)
+{
+ /*
+ * 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.
+ */
+ if (!(port->flags & UPF_HARD_FLOW) ||
+ (port->rs485.flags & SER_RS485_ENABLED))
+ 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 +245,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 +284,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;
--
2.55.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-08-06 15:42 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-06 15:42 [PATCH 0/1] Implement BCM2835 AUX RTS/CTS auto flow control Jan Čermák
2026-08-06 15:42 ` [PATCH 1/1] serial: 8250_bcm2835aux: Add support for " Jan Čermák
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox