[PATCH v3 2/4] serial: sh-sci: Keep TX enabled on TEI while data is pending

From: Biju Das

Date: Mon Oct 26 2026 - 07:44:13 EST


From: Biju Das <biju.das.jz@xxxxxxxxxxxxxx>

sci_tx_end_interrupt() handles the transmit-end interrupt (TEI) on
SCI and RSCI ports by clearing TE and TEIE, which turns the
transmitter off. It does this unconditionally, even if the xmit kfifo
was refilled, or an x_char was queued, after the last byte was written
to the data register and before TEI fired. The pending data is then not
sent until the transmitter is re-enabled, so output stalls or is lost.

Check for pending data (x_char, or a non-empty xmit kfifo on a port
that is not stopped) before shutting the transmitter down. If there is
any, clear TEIE, set TIE and hand over to the port's transmit_chars()
op so transmission continues normally. The existing TE/TEIE teardown
path only runs once nothing is left to send.

When the port is stopped with data still queued, the transmit_chars()
paths (sci_transmit_chars() and rsci_transmit_chars()) now also swap
TIE for TEIE, as they already did for an empty kfifo. Without this, TIE
would keep firing with nothing the driver is allowed to send, and TEI
would never be raised to complete the transmission.

To keep the handler independent of the register layout, add tie and
teie masks to struct sci_port_params_bits and populate them for the
SCI, SCIF and RSCI variants.

Fixes: d61ae331d6f3 ("tty: serial: sh-sci: Add support for tx end interrupt handling")
Signed-off-by: Biju Das <biju.das.jz@xxxxxxxxxxxxxx>
---
v2->v3:
* Updated commit description
* When the port is stopped with data still queued, the transmit_chars()
paths (sci_transmit_chars() and rsci_transmit_chars()) now also swap
TIE for TEIE, as they already did for an empty kfifo. Without this, TIE
would keep firing with nothing the driver is allowed to send, and TEI
would never be raised to complete the transmission.
* Replaced sci_tx_interrupt() with port's transmit_chars() to avoid
lock drop race in sci_tx_end_interrupt() before TX handoff.
v1->v2:
* Updated commit header and description.
* Take the port lock before reading the control register, so the
read-modify-write in sci_tx_end_interrupt() is protected.
* Extended the pending-data check in sci_tx_end_interrupt() to cover
x_char and a stopped port, not only a non-empty xmit kfifo.
---
drivers/tty/serial/rsci.c | 4 +++-
drivers/tty/serial/sh-sci-common.h | 2 ++
drivers/tty/serial/sh-sci.c | 47 +++++++++++++++++++++++++++++---------
3 files changed, 41 insertions(+), 12 deletions(-)

diff --git a/drivers/tty/serial/rsci.c b/drivers/tty/serial/rsci.c
index 40db9daa4272..0391111db8ae 100644
--- a/drivers/tty/serial/rsci.c
+++ b/drivers/tty/serial/rsci.c
@@ -465,7 +465,7 @@ static void rsci_transmit_chars(struct uart_port *port)
if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
uart_write_wakeup(port);

- if (kfifo_is_empty(&tport->xmit_fifo)) {
+ if (kfifo_is_empty(&tport->xmit_fifo) || stopped) {
ctrl = rsci_serial_in(port, CCR0);
ctrl &= ~CCR0_TIE;
ctrl |= CCR0_TEIE;
@@ -627,6 +627,8 @@ static const struct sci_port_params_bits rsci_port_param_bits = {
.rxtx_enable = CCR0_RE | CCR0_TE,
.te_clear = CCR0_TE | CCR0_TEIE,
.poll_sent_bits = CSR_TDRE | CSR_TEND,
+ .tie = CCR0_TIE,
+ .teie = CCR0_TEIE
};

static const struct sci_port_params rsci_rzg3e_port_params = {
diff --git a/drivers/tty/serial/sh-sci-common.h b/drivers/tty/serial/sh-sci-common.h
index 01ff9fced803..273856fbc20b 100644
--- a/drivers/tty/serial/sh-sci-common.h
+++ b/drivers/tty/serial/sh-sci-common.h
@@ -56,6 +56,8 @@ struct sci_port_params_bits {
unsigned int rxtx_enable;
unsigned int te_clear;
unsigned int poll_sent_bits;
+ unsigned int tie;
+ unsigned int teie;
};

struct sci_common_regs {
diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c
index 9eb297dbc901..6df25fef5a5f 100644
--- a/drivers/tty/serial/sh-sci.c
+++ b/drivers/tty/serial/sh-sci.c
@@ -261,13 +261,17 @@ static bool sci_uart_earlycon_dev_probing;
static const struct sci_port_params_bits sci_sci_port_params_bits = {
.rxtx_enable = SCSCR_RE | SCSCR_TE,
.te_clear = SCSCR_TE | SCSCR_TEIE,
- .poll_sent_bits = SCI_TDRE | SCI_TEND
+ .poll_sent_bits = SCI_TDRE | SCI_TEND,
+ .tie = SCSCR_TIE,
+ .teie = SCSCR_TEIE
};

static const struct sci_port_params_bits sci_scif_port_params_bits = {
.rxtx_enable = SCSCR_RE | SCSCR_TE,
.te_clear = SCSCR_TE | SCSCR_TEIE,
- .poll_sent_bits = SCIF_TDFE | SCIF_TEND
+ .poll_sent_bits = SCIF_TDFE | SCIF_TEND,
+ .tie = SCSCR_TIE,
+ .teie = SCSCR_TEIE
};

static const struct sci_common_regs sci_common_regs = {
@@ -1007,6 +1011,20 @@ static int sci_rxfill(struct uart_port *port)
* the interrupt related routines *
* ********************************************************************** */

+static void sci_clr_tie_set_teie(struct uart_port *port)
+{
+ struct sci_port *s = to_sci_port(port);
+ unsigned short ctrl;
+
+ if (s->type != PORT_SCI)
+ return;
+
+ ctrl = sci_serial_in(port, SCSCR);
+ ctrl &= ~SCSCR_TIE;
+ ctrl |= SCSCR_TEIE;
+ sci_serial_out(port, SCSCR, ctrl);
+}
+
static void sci_transmit_chars(struct uart_port *port)
{
struct tty_port *tport = &port->state->port;
@@ -1050,14 +1068,10 @@ static void sci_transmit_chars(struct uart_port *port)
if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
uart_write_wakeup(port);
if (kfifo_is_empty(&tport->xmit_fifo)) {
- if (s->type == PORT_SCI) {
- ctrl = sci_serial_in(port, SCSCR);
- ctrl &= ~SCSCR_TIE;
- ctrl |= SCSCR_TEIE;
- sci_serial_out(port, SCSCR, ctrl);
- }
-
+ sci_clr_tie_set_teie(port);
sci_stop_tx(port);
+ } else if (stopped) {
+ sci_clr_tie_set_teie(port);
}
}

@@ -2012,6 +2026,7 @@ static irqreturn_t sci_tx_end_interrupt(int irq, void *ptr)
{
struct uart_port *port = ptr;
struct sci_port *s = to_sci_port(port);
+ struct tty_port *tport = &port->state->port;
const struct sci_common_regs *regs = s->params->common_regs;
unsigned long flags;
u32 ctrl;
@@ -2020,8 +2035,18 @@ static irqreturn_t sci_tx_end_interrupt(int irq, void *ptr)
return sci_tx_interrupt(irq, ptr);

uart_port_lock_irqsave(port, &flags);
- ctrl = s->ops->read_reg(port, regs->control) &
- ~(s->params->param_bits->te_clear);
+ ctrl = s->ops->read_reg(port, regs->control);
+ if (port->x_char || (!kfifo_is_empty(&tport->xmit_fifo) &&
+ !uart_tx_stopped(port))) {
+ ctrl &= ~(s->params->param_bits->teie);
+ ctrl |= s->params->param_bits->tie;
+ s->ops->write_reg(port, regs->control, ctrl);
+ s->ops->transmit_chars(port);
+ uart_port_unlock_irqrestore(port, flags);
+ return IRQ_HANDLED;
+ }
+
+ ctrl &= ~(s->params->param_bits->te_clear);
s->ops->write_reg(port, regs->control, ctrl);
uart_port_unlock_irqrestore(port, flags);


--
2.43.0