* [PATCH v2 1/4] serial: 8250_dw: Keep the BUSY-safe divisor hook when DLF is present
2026-10-19 20:06 [PATCH v2 0/4] serial: 8250_dw: Allwinner A733 UART support Vinicius Pedrosa
@ 2026-10-19 20:06 ` Vinicius Pedrosa
2026-10-19 20:06 ` [PATCH v2 2/4] serial: 8250_dw: Only accept a contiguous DLF readback Vinicius Pedrosa
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Vinicius Pedrosa @ 2026-10-19 20:06 UTC (permalink / raw)
To: linux-serial
Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, andriy.shevchenko,
ilpo.jarvinen, devicetree, linux-kernel, linux-sunxi,
Andre Przywara, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Enzo Adriano, stable
dw8250_probe() installs dw8250_set_divisor(), which brackets the
DLAB/DLL/DLH sequence with dw8250_idle_enter() and dw8250_idle_exit()
so a BUSY UART cannot drop the divisor LCR write.
dw8250_setup_port() runs afterwards and replaces that hook with the
dwlib divisor hook whenever it detects DLF. The dwlib hook programs the
divisor through serial8250_do_set_divisor() without entering idle,
leaving the LCR|DLAB write exposed to BUSY.
When termios changes while the UART is transmitting, that LCR|DLAB
write can be ignored and dw8250_check_lcr() retries it between
dw8250_idle_enter() and dw8250_idle_exit(). dw8250_idle_exit() then
restores IER while DLAB is still set, so the value lands in DLH. The
real IER stays at the zero written by dw8250_idle_enter(), and the port
gets no further TX or RX interrupts while up->ier says they're enabled.
Do not replace a divisor hook the driver has already installed. Program
DLF from the BUSY-safe hook, inside the idle section, through a dwlib
helper that checks dlf_size.
Observed on an Allwinner A733 (Radxa Cubie A7Z), where
dw8250_setup_port() mistakes the RS485 control register for DLF (see the
next patch): the first interactive tcsetattr() during console output
left hardware IER at 0 and up->ier at 0x07. The shell never printed its
prompt or read input. With this change alone the same boot reached the
prompt and echoed input, and hardware IER matched up->ier afterwards.
Fixes: a7b9ce39fbe4 ("serial: 8250_dw: Ensure BUSY is deasserted")
Cc: stable@vger.kernel.org
Signed-off-by: Vinicius Pedrosa <vinicius.eduardo.pedrosa@gmail.com>
---
drivers/tty/serial/8250/8250_dw.c | 2 ++
drivers/tty/serial/8250/8250_dwlib.c | 16 ++++++++++++++--
drivers/tty/serial/8250/8250_dwlib.h | 1 +
3 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index eca02e8bd6d1..4c86aaf9c121 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -197,6 +197,8 @@ static void dw8250_set_divisor(struct uart_port *p, unsigned int baud,
if (ret < 0)
return;
+ __dw8250_set_divisor(p, quot_frac);
+
serial_port_out(p, UART_LCR, up->lcr | UART_LCR_DLAB);
if (!(serial_port_in(p, UART_LCR) & UART_LCR_DLAB))
goto idle_failed;
diff --git a/drivers/tty/serial/8250/8250_dwlib.c b/drivers/tty/serial/8250/8250_dwlib.c
index 9bb02a4ab11f..325793f20256 100644
--- a/drivers/tty/serial/8250/8250_dwlib.c
+++ b/drivers/tty/serial/8250/8250_dwlib.c
@@ -36,10 +36,20 @@ static unsigned int dw8250_get_divisor(struct uart_port *p, unsigned int baud,
return quot;
}
+/* Program the fractional part of the divisor, if the UART has one */
+void __dw8250_set_divisor(struct uart_port *p, unsigned int quot_frac)
+{
+ struct dw8250_port_data *d = p->private_data;
+
+ if (d->dlf_size)
+ dw8250_writel_ext(p, DW_UART_DLF, quot_frac);
+}
+EXPORT_SYMBOL_GPL(__dw8250_set_divisor);
+
static void dw8250_set_divisor(struct uart_port *p, unsigned int baud,
unsigned int quot, unsigned int quot_frac)
{
- dw8250_writel_ext(p, DW_UART_DLF, quot_frac);
+ __dw8250_set_divisor(p, quot_frac);
serial8250_do_set_divisor(p, baud, quot);
}
@@ -218,7 +228,9 @@ void dw8250_setup_port(struct uart_port *p)
if (reg) {
pd->dlf_size = fls(reg);
p->get_divisor = dw8250_get_divisor;
- p->set_divisor = dw8250_set_divisor;
+ /* Keep a hook the driver has already installed */
+ if (!p->set_divisor)
+ p->set_divisor = dw8250_set_divisor;
}
reg = dw8250_readl_ext(p, DW_UART_UCV);
diff --git a/drivers/tty/serial/8250/8250_dwlib.h b/drivers/tty/serial/8250/8250_dwlib.h
index 39fcb1b323ad..4bb0ccb3e8ef 100644
--- a/drivers/tty/serial/8250/8250_dwlib.h
+++ b/drivers/tty/serial/8250/8250_dwlib.h
@@ -99,6 +99,7 @@ struct dw8250_port_data {
void dw8250_do_set_termios(struct uart_port *p, struct ktermios *termios, const struct ktermios *old);
void dw8250_setup_port(struct uart_port *p);
+void __dw8250_set_divisor(struct uart_port *p, unsigned int quot_frac);
static inline u32 dw8250_readl_ext(struct uart_port *p, int offset)
{
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH v2 2/4] serial: 8250_dw: Only accept a contiguous DLF readback
2026-10-19 20:06 [PATCH v2 0/4] serial: 8250_dw: Allwinner A733 UART support Vinicius Pedrosa
2026-10-19 20:06 ` [PATCH v2 1/4] serial: 8250_dw: Keep the BUSY-safe divisor hook when DLF is present Vinicius Pedrosa
@ 2026-10-19 20:06 ` Vinicius Pedrosa
2026-10-20 6:11 ` Jiri Slaby
2026-10-19 20:06 ` [PATCH v2 3/4] dt-bindings: serial: snps-dw-apb-uart: Add Allwinner A733 Vinicius Pedrosa
2026-10-19 20:06 ` [PATCH v2 4/4] serial: 8250_dw: Add Allwinner A733 UART Vinicius Pedrosa
3 siblings, 1 reply; 6+ messages in thread
From: Vinicius Pedrosa @ 2026-10-19 20:06 UTC (permalink / raw)
To: linux-serial
Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, andriy.shevchenko,
ilpo.jarvinen, devicetree, linux-kernel, linux-sunxi,
Andre Przywara, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Enzo Adriano
dw8250_setup_port() writes all ones to offset 0xc0 and treats any
nonzero readback as the DLF register. DLF is a bit field starting at
bit 0, so a real one reads back as a contiguous 2^n - 1 mask. On some
Allwinner SoCs offset 0xc0 is the RS485 control register instead
(UART_485_CTL in the A733 User Manual), whose implemented bits are not
contiguous. The probe still takes its readback for a DLF of fls(readback)
bits, and every later divisor change writes the fractional divisor into
the RS485 control register.
Only accept a readback of the form 2^n - 1.
The probe itself is unchanged: it still writes all ones to offset 0xc0
and then restores the old value, so this only changes how the readback
is interpreted.
The A733 reads back 0x19f, and Andre Przywara observed 0x9f on the
A523. Without this check the A733 RS485 control register reads 0xb after
boot; with it, the register stays 0 across 300 termios toggle and
restore cycles.
Suggested-by: Andre Przywara <andre.przywara@arm.com>
Signed-off-by: Vinicius Pedrosa <vinicius.eduardo.pedrosa@gmail.com>
---
drivers/tty/serial/8250/8250_dwlib.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/tty/serial/8250/8250_dwlib.c b/drivers/tty/serial/8250/8250_dwlib.c
index 325793f20256..1b0192efe162 100644
--- a/drivers/tty/serial/8250/8250_dwlib.c
+++ b/drivers/tty/serial/8250/8250_dwlib.c
@@ -225,7 +225,11 @@ void dw8250_setup_port(struct uart_port *p)
reg = dw8250_readl_ext(p, DW_UART_DLF);
dw8250_writel_ext(p, DW_UART_DLF, old_dlf);
- if (reg) {
+ /*
+ * DLF is a bit field starting at bit 0, so a real one reads back as
+ * 2^n - 1. Any other value means this offset is something else.
+ */
+ if (reg && !(reg & (reg + 1))) {
pd->dlf_size = fls(reg);
p->get_divisor = dw8250_get_divisor;
/* Keep a hook the driver has already installed */
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH v2 2/4] serial: 8250_dw: Only accept a contiguous DLF readback
2026-10-19 20:06 ` [PATCH v2 2/4] serial: 8250_dw: Only accept a contiguous DLF readback Vinicius Pedrosa
@ 2026-10-20 6:11 ` Jiri Slaby
0 siblings, 0 replies; 6+ messages in thread
From: Jiri Slaby @ 2026-10-20 6:11 UTC (permalink / raw)
To: Vinicius Pedrosa, linux-serial
Cc: gregkh, robh, krzk+dt, conor+dt, andriy.shevchenko,
ilpo.jarvinen, devicetree, linux-kernel, linux-sunxi,
Andre Przywara, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Enzo Adriano
On 19. 10. 26, 22:06, Vinicius Pedrosa wrote:
> --- a/drivers/tty/serial/8250/8250_dwlib.c
> +++ b/drivers/tty/serial/8250/8250_dwlib.c
> @@ -225,7 +225,11 @@ void dw8250_setup_port(struct uart_port *p)
> reg = dw8250_readl_ext(p, DW_UART_DLF);
> dw8250_writel_ext(p, DW_UART_DLF, old_dlf);
>
> - if (reg) {
> + /*
> + * DLF is a bit field starting at bit 0, so a real one reads back as
> + * 2^n - 1. Any other value means this offset is something else.
> + */
> + if (reg && !(reg & (reg + 1))) {
Do I understand correctly, that this equivalent to:
if (is_power_of_2(reg + 1))
?
thanks,
--
js
suse labs
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 3/4] dt-bindings: serial: snps-dw-apb-uart: Add Allwinner A733
2026-10-19 20:06 [PATCH v2 0/4] serial: 8250_dw: Allwinner A733 UART support Vinicius Pedrosa
2026-10-19 20:06 ` [PATCH v2 1/4] serial: 8250_dw: Keep the BUSY-safe divisor hook when DLF is present Vinicius Pedrosa
2026-10-19 20:06 ` [PATCH v2 2/4] serial: 8250_dw: Only accept a contiguous DLF readback Vinicius Pedrosa
@ 2026-10-19 20:06 ` Vinicius Pedrosa
2026-10-19 20:06 ` [PATCH v2 4/4] serial: 8250_dw: Add Allwinner A733 UART Vinicius Pedrosa
3 siblings, 0 replies; 6+ messages in thread
From: Vinicius Pedrosa @ 2026-10-19 20:06 UTC (permalink / raw)
To: linux-serial
Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, andriy.shevchenko,
ilpo.jarvinen, devicetree, linux-kernel, linux-sunxi,
Andre Przywara, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Enzo Adriano, Conor Dooley
The Allwinner A733 UART is a DesignWare APB UART, but it needs
SoC-specific handling so the driver can avoid stopping its clock during
termios changes.
Add an A733-specific compatible, with "snps,dw-apb-uart" as the
fallback.
Signed-off-by: Vinicius Pedrosa <vinicius.eduardo.pedrosa@gmail.com>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
---
Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml
index e2ca126d58ba..532392dd54cf 100644
--- a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml
+++ b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml
@@ -60,6 +60,7 @@ properties:
- const: renesas,rzn1-uart
- items:
- enum:
+ - allwinner,sun60i-a733-uart
- anlogic,dr1v90-uart
- brcm,bcm11351-dw-apb-uart
- brcm,bcm21664-dw-apb-uart
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH v2 4/4] serial: 8250_dw: Add Allwinner A733 UART
2026-10-19 20:06 [PATCH v2 0/4] serial: 8250_dw: Allwinner A733 UART support Vinicius Pedrosa
` (2 preceding siblings ...)
2026-10-19 20:06 ` [PATCH v2 3/4] dt-bindings: serial: snps-dw-apb-uart: Add Allwinner A733 Vinicius Pedrosa
@ 2026-10-19 20:06 ` Vinicius Pedrosa
3 siblings, 0 replies; 6+ messages in thread
From: Vinicius Pedrosa @ 2026-10-19 20:06 UTC (permalink / raw)
To: linux-serial
Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, andriy.shevchenko,
ilpo.jarvinen, devicetree, linux-kernel, linux-sunxi,
Andre Przywara, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Enzo Adriano
On the A733, dw8250_set_termios() stops the UART clock around
clk_set_rate() on every termios change that reaches the driver. If TX is
active at that moment, the character being shifted out is corrupted.
No baud rate change is required to trigger the problem. Toggling only
CLOCAL or CSTOPB 300 times during continuous console output corrupted
bytes in every run without the quirk and in none with it.
Use the existing SKIP_SET_RATE quirk, as the JH7100 and SG2044 UARTs do.
Signed-off-by: Vinicius Pedrosa <vinicius.eduardo.pedrosa@gmail.com>
---
drivers/tty/serial/8250/8250_dw.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 4c86aaf9c121..718ec44135e4 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -906,6 +906,7 @@ static const struct dw8250_platform_data dw8250_tda54 = {
static const struct of_device_id dw8250_of_match[] = {
{ .compatible = "snps,dw-apb-uart", .data = &dw8250_dw_apb },
+ { .compatible = "allwinner,sun60i-a733-uart", .data = &dw8250_skip_set_rate_data },
{ .compatible = "cavium,octeon-3860-uart", .data = &dw8250_octeon_3860_data },
{ .compatible = "marvell,armada-38x-uart", .data = &dw8250_armada_38x_data },
{ .compatible = "renesas,rzn1-uart", .data = &dw8250_renesas_rzn1_data },
^ permalink raw reply [flat|nested] 6+ messages in thread