Commit 8002d6d6 authored by Ilpo Järvinen's avatar Ilpo Järvinen Committed by Greg Kroah-Hartman
Browse files

serial: 8250_dw: Avoid unnecessary LCR writes



When DW UART is configured with BUSY flag, LCR writes may not always
succeed which can make any LCR write complex and very expensive.
Performing write directly can trigger IRQ and the driver has to perform
complex and distruptive sequence while retrying the write.

Therefore, it's better to avoid doing LCR write that would not change
the value of the LCR register. Add LCR write avoidance code into the
8250_dw driver's .serial_out() functions.

Reported-by: default avatarBandal, Shankar <shankar.bandal@intel.com>
Tested-by: default avatarBandal, Shankar <shankar.bandal@intel.com>
Tested-by: default avatarMurthy, Shanth <shanth.murthy@intel.com>
Cc: stable <stable@kernel.org>
Reviewed-by: default avatarAndy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: default avatarIlpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://patch.msgid.link/20260203171049.4353-3-ilpo.jarvinen@linux.intel.com


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 59a33d83
Loading
Loading
Loading
Loading
+31 −0
Original line number Diff line number Diff line
@@ -181,6 +181,22 @@ static void dw8250_check_lcr(struct uart_port *p, unsigned int offset, u32 value
	 */
}

/*
 * With BUSY, LCR writes can be very expensive (IRQ + complex retry logic).
 * If the write does not change the value of the LCR register, skip it entirely.
 */
static bool dw8250_can_skip_reg_write(struct uart_port *p, unsigned int offset, u32 value)
{
	struct dw8250_data *d = to_dw8250_data(p->private_data);
	u32 lcr;

	if (offset != UART_LCR || d->uart_16550_compatible)
		return false;

	lcr = serial_port_in(p, offset);
	return lcr == value;
}

/* Returns once the transmitter is empty or we run out of retries */
static void dw8250_tx_wait_empty(struct uart_port *p)
{
@@ -207,12 +223,18 @@ static void dw8250_tx_wait_empty(struct uart_port *p)

static void dw8250_serial_out(struct uart_port *p, unsigned int offset, u32 value)
{
	if (dw8250_can_skip_reg_write(p, offset, value))
		return;

	writeb(value, p->membase + (offset << p->regshift));
	dw8250_check_lcr(p, offset, value);
}

static void dw8250_serial_out38x(struct uart_port *p, unsigned int offset, u32 value)
{
	if (dw8250_can_skip_reg_write(p, offset, value))
		return;

	/* Allow the TX to drain before we reconfigure */
	if (offset == UART_LCR)
		dw8250_tx_wait_empty(p);
@@ -237,6 +259,9 @@ static u32 dw8250_serial_inq(struct uart_port *p, unsigned int offset)

static void dw8250_serial_outq(struct uart_port *p, unsigned int offset, u32 value)
{
	if (dw8250_can_skip_reg_write(p, offset, value))
		return;

	value &= 0xff;
	__raw_writeq(value, p->membase + (offset << p->regshift));
	/* Read back to ensure register write ordering. */
@@ -248,6 +273,9 @@ static void dw8250_serial_outq(struct uart_port *p, unsigned int offset, u32 val

static void dw8250_serial_out32(struct uart_port *p, unsigned int offset, u32 value)
{
	if (dw8250_can_skip_reg_write(p, offset, value))
		return;

	writel(value, p->membase + (offset << p->regshift));
	dw8250_check_lcr(p, offset, value);
}
@@ -261,6 +289,9 @@ static u32 dw8250_serial_in32(struct uart_port *p, unsigned int offset)

static void dw8250_serial_out32be(struct uart_port *p, unsigned int offset, u32 value)
{
	if (dw8250_can_skip_reg_write(p, offset, value))
		return;

	iowrite32be(value, p->membase + (offset << p->regshift));
	dw8250_check_lcr(p, offset, value);
}