[PATCH v5 05/14] serial: 8250_mxpcie: offload XON/XOFF flow control to MUEx50 hardware

Crescent Hsieh <[email protected]> Fri, 31 Jul 2026 15:48:11 +0800
Newsgroups org.kernel.vger.linux-serial,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
The MUEx50 UART can handle in-band software flow control (XON/XOFF)
directly in hardware.

Program the on-chip XON/XOFF characters from termios settings and enable
the corresponding MUEx50 flow control modes when IXON or IXOFF is
requested. Provide throttle and unthrottle callbacks so RX can be
stopped and resumed cleanly.

Signed-off-by: Crescent Hsieh <[email protected]>
---
 drivers/tty/serial/8250/8250_mxpcie.c | 53 +++++++++++++++++++++++++--
 1 file changed, 50 insertions(+), 3 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c
index 25ca43225e7a..39eee8a9b82d 100644
--- a/drivers/tty/serial/8250/8250_mxpcie.c
+++ b/drivers/tty/serial/8250/8250_mxpcie.c
@@ -61,8 +61,20 @@
 #define MOXA_PUART_EFR_AUTO_RTS			BIT(6)
 #define MOXA_PUART_EFR_AUTO_CTS			BIT(7)
 #define MOXA_PUART_EFR_RX_FLOW_MASK		GENMASK(1, 0)
+#define MOXA_PUART_EFR_RX_FLOW_DISABLED		0x0
+#define MOXA_PUART_EFR_RX_FLOW_XON2_XOFF2	0x1
+#define MOXA_PUART_EFR_RX_FLOW_XON1_XOFF1	0x2
+#define MOXA_PUART_EFR_RX_FLOW_COPY_TX		0x3
 #define MOXA_PUART_EFR_TX_FLOW_MASK		GENMASK(3, 2)
-
+#define MOXA_PUART_EFR_TX_FLOW_DISABLED		0x0
+#define MOXA_PUART_EFR_TX_FLOW_XON2_XOFF2	0x1
+#define MOXA_PUART_EFR_TX_FLOW_XON1_XOFF1	0x2
+#define MOXA_PUART_EFR_TX_FLOW_RESERVED		0x3
+
+#define MOXA_PUART_XON1		0x0B
+#define MOXA_PUART_XON2		0x0C
+#define MOXA_PUART_XOFF1	0x0D
+#define MOXA_PUART_XOFF2	0x0E
 #define MOXA_PUART_TTL		0x10	/* Tx Interrupt Trigger Level */
 #define MOXA_PUART_RTL		0x11	/* Rx Interrupt Trigger Level */
 #define MOXA_PUART_FCL		0x12	/* Flow Control Low Trigger Level */
@@ -159,11 +171,11 @@ static void mxpcie8250_set_termios(struct uart_port *port,
 	struct uart_8250_port *up = up_to_u8250p(port);
 	struct tty_struct *tty = port->state->port.tty;
 	unsigned int cflag = tty->termios.c_cflag;
-	u8 efr;
+	u8 efr, val;
 
 	serial8250_do_set_termios(port, new, old);
 
-	up->port.status &= ~(UPSTAT_AUTORTS | UPSTAT_AUTOCTS);
+	up->port.status &= ~(UPSTAT_AUTORTS | UPSTAT_AUTOCTS | UPSTAT_AUTOXOFF);
 
 	efr = serial_in(up, MOXA_PUART_EFR);
 	efr &= ~(MOXA_PUART_EFR_AUTO_RTS | MOXA_PUART_EFR_AUTO_CTS);
@@ -172,6 +184,21 @@ static void mxpcie8250_set_termios(struct uart_port *port,
 		efr |= (MOXA_PUART_EFR_AUTO_RTS | MOXA_PUART_EFR_AUTO_CTS);
 		up->port.status |= (UPSTAT_AUTORTS | UPSTAT_AUTOCTS);
 	}
+	/* Set on-chip software flow control character */
+	serial_out(up, MOXA_PUART_XON1, START_CHAR(tty));
+	serial_out(up, MOXA_PUART_XON2, START_CHAR(tty));
+	serial_out(up, MOXA_PUART_XOFF1, STOP_CHAR(tty));
+	serial_out(up, MOXA_PUART_XOFF2, STOP_CHAR(tty));
+
+	val = I_IXON(tty) ? MOXA_PUART_EFR_RX_FLOW_XON1_XOFF1 : MOXA_PUART_EFR_RX_FLOW_DISABLED;
+	FIELD_MODIFY(MOXA_PUART_EFR_RX_FLOW_MASK, &efr, val);
+
+	val = I_IXOFF(tty) ? MOXA_PUART_EFR_TX_FLOW_XON1_XOFF1 : MOXA_PUART_EFR_TX_FLOW_DISABLED;
+	FIELD_MODIFY(MOXA_PUART_EFR_TX_FLOW_MASK, &efr, val);
+
+	if (I_IXOFF(tty))
+		up->port.status |= UPSTAT_AUTOXOFF;
+
 	serial_out(up, MOXA_PUART_EFR, efr);
 }
 
@@ -218,6 +245,24 @@ static void mxpcie8250_shutdown(struct uart_port *port)
 	serial8250_do_shutdown(port);
 }
 
+static void mxpcie8250_throttle(struct uart_port *port)
+{
+	guard(uart_port_lock_irqsave)(port);
+
+	port->ops->stop_rx(port);
+}
+
+static void mxpcie8250_unthrottle(struct uart_port *port)
+{
+	struct uart_8250_port *up = up_to_u8250p(port);
+
+	guard(uart_port_lock_irqsave)(port);
+
+	up->ier |= UART_IER_RLSI | UART_IER_RDI;
+	port->read_status_mask |= UART_LSR_DR;
+	serial_out(up, UART_IER, up->ier);
+}
+
 static void mxpcie8250_init_board(struct pci_dev *pdev, struct mxpcie8250 *priv)
 {
 	void __iomem *bar2_base = priv->bar2_base;
@@ -315,6 +360,8 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id
 	up.port.set_termios = mxpcie8250_set_termios;
 	up.port.startup = mxpcie8250_startup;
 	up.port.shutdown = mxpcie8250_shutdown;
+	up.port.throttle = mxpcie8250_throttle;
+	up.port.unthrottle = mxpcie8250_unthrottle;
 
 	for (unsigned int i = 0; i < num_ports; i++) {
 		mxpcie8250_setup_port(pdev, priv, &up, i);
-- 
2.45.2