[PATCH v4 1/2] serial: amba-pl011: finish a transmission still running when RS485 is enabled

From: Nicolai Buchwitz

Date: Wed Oct 21 2026 - 07:51:09 EST


Disabling RS485 during a transmission keeps it running. If RS485 is
enabled again before it ends, the state is OFF while data is still going
out. The TX path does not end a transmission in state OFF, so nothing
sets the idle RTS level afterwards and the transceiver keeps driving the
bus.

Hand such a transmission back to RS485 when it is enabled on an open
port. While PIO or DMA still feeds the FIFO, assert RTS and mark it as
sending, so that the TX path ends it like any other. With only the FIFO
left, drain it from trigger_stop_tx instead. Data that is queued but not
started yet, for example while output is stopped, is not part of the
transmission.

Fixes: 97f85204275a ("serial: amba-pl011: keep transmitter enabled when disabling RS485")
Cc: stable@xxxxxxxxxxxxxxx # 6.18.x: 1b6d660e038b: serial: amba-pl011: drive the bus during break in RS485 mode
Cc: stable@xxxxxxxxxxxxxxx # 6.18.x
Signed-off-by: Nicolai Buchwitz <nb@xxxxxxxxxxx>
---
drivers/tty/serial/amba-pl011.c | 57 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 57 insertions(+)

diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c
index 79776a882ae35..1edbe75611e63 100644
--- a/drivers/tty/serial/amba-pl011.c
+++ b/drivers/tty/serial/amba-pl011.c
@@ -1285,6 +1285,11 @@ static inline bool pl011_dma_tx_queued(struct uart_amba_port *uap)
return uap->dmatx.queued;
}

+static inline bool pl011_dma_tx_active(struct uart_amba_port *uap)
+{
+ return uap->dmacr & UART011_TXDMAE;
+}
+
#else
/* Blank functions if the DMA engine is not available */
static inline void pl011_dma_remove(struct uart_amba_port *uap)
@@ -1341,6 +1346,11 @@ static inline bool pl011_dma_tx_queued(struct uart_amba_port *uap)
return false;
}

+static inline bool pl011_dma_tx_active(struct uart_amba_port *uap)
+{
+ return false;
+}
+
#define pl011_dma_flush_buffer NULL
#endif

@@ -1734,6 +1744,12 @@ static unsigned int pl011_tx_empty(struct uart_port *port)
0 : TIOCSER_TEMT;
}

+/* PIO or DMA still feeds the FIFO */
+static bool pl011_tx_active(struct uart_amba_port *uap)
+{
+ return (uap->im & UART011_TXIM) || pl011_dma_tx_active(uap);
+}
+
static void pl011_maybe_set_bit(bool cond, unsigned int *ptr, unsigned int mask)
{
if (cond)
@@ -2454,6 +2470,42 @@ static int pl011_verify_port(struct uart_port *port, struct serial_struct *ser)
return ret;
}

+/*
+ * Data from before RS485 was enabled is still going out. Finish it as an
+ * RS485 transmission, so that its end sets the idle level.
+ */
+static void pl011_rs485_tx_resume(struct uart_amba_port *uap,
+ const struct serial_rs485 *rs485)
+{
+ u32 cr = pl011_read(uap, REG_CR);
+
+ cr |= UART011_CR_TXE;
+ if (!(rs485->flags & SER_RS485_RX_DURING_TX))
+ cr &= ~UART011_CR_RXE;
+ if (rs485->flags & SER_RS485_RTS_ON_SEND)
+ cr &= ~UART011_CR_RTS;
+ else
+ cr |= UART011_CR_RTS;
+ pl011_write(cr, uap, REG_CR);
+
+ /* pl011_break_ctl() ends the transmission when the break ends */
+ if (pl011_read(uap, REG_LCRH_TX) & UART01x_LCRH_BRK) {
+ uap->rs485_break = true;
+ uap->rs485_tx_state = SEND;
+ return;
+ }
+
+ if (pl011_tx_active(uap)) {
+ uap->rs485_tx_state = SEND;
+ return;
+ }
+
+ /* Only the FIFO is left. The timer drains it with the new settings */
+ uap->rs485_tx_state = WAIT_AFTER_SEND;
+ hrtimer_start(&uap->trigger_stop_tx, uap->rs485_tx_drain_interval,
+ HRTIMER_MODE_REL);
+}
+
static int pl011_rs485_config(struct uart_port *port, struct ktermios *termios,
struct serial_rs485 *rs485)
{
@@ -2483,6 +2535,11 @@ static int pl011_rs485_config(struct uart_port *port, struct ktermios *termios,
if (tx_pending && !uart_tx_stopped(port) &&
!pl011_dma_tx_start(uap))
pl011_start_tx_pio(uap);
+ } else if ((rs485->flags & SER_RS485_ENABLED) &&
+ uap->rs485_tx_state == OFF &&
+ tty_port_initialized(&port->state->port) &&
+ (pl011_tx_active(uap) || !pl011_tx_empty(port))) {
+ pl011_rs485_tx_resume(uap, rs485);
} else if (port->rs485.flags & SER_RS485_ENABLED) {
pl011_rs485_tx_stop(uap);
}

--
2.53.0