* [PATCH 0/3] serial: 8250_dw: Allwinner A733 UART support
@ 2026-10-05 17:25 Vinicius Pedrosa
2026-10-05 17:25 ` [PATCH 1/3] serial: 8250_dw: Keep the BUSY-safe divisor hook when DLF is present Vinicius Pedrosa
` (2 more replies)
0 siblings, 3 replies; 8+ messages in thread
From: Vinicius Pedrosa @ 2026-10-05 17:25 UTC (permalink / raw)
To: linux-serial
Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, andriy.shevchenko,
ilpo.jarvinen, devicetree, linux-kernel, linux-sunxi,
Enzo Adriano
I'm bringing up mainline on the Radxa Cubie A7Z (Allwinner A733). The
console worked until the first termios change from userspace, then
stopped taking interrupts.
Patch 1 fixes that in the generic driver: with a DLF register present,
dw8250_setup_port() replaces the BUSY-safe divisor hook, and the LCR
retry path then restores IER into DLH. It's tagged for stable.
Patches 2-3 add an A733 compatible with two quirks: no clk_set_rate()
around termios (the baud clock is a fixed bus gate), and no DLF probe
(offset 0xc0 is an RS485 control register).
Tested on a Cubie A7Z, with local DT and clock changes that aren't part
of this series. The console worked in both directions and a termios
stress loop ran without stalls or corrupted bytes. The no-DLF check in
patch 3 was reworked after that run (an if block in place of a goto),
and this exact revision hasn't been booted yet.
The A733 DTSI isn't in linux-next yet. Enzo Adriano's posted version
uses plain "snps,dw-apb-uart" for uart0, so it would need the new
compatible once this lands.
Based on tty-next.
Vinicius Pedrosa (3):
serial: 8250_dw: Keep the BUSY-safe divisor hook when DLF is present
dt-bindings: serial: snps-dw-apb-uart: Add Allwinner A733
serial: 8250_dw: Add Allwinner A733 UART
.../bindings/serial/snps-dw-apb-uart.yaml | 1 +
drivers/tty/serial/8250/8250_dw.c | 25 ++++++++++++++++++-
drivers/tty/serial/8250/8250_dwlib.c | 22 ++++++++--------
drivers/tty/serial/8250/8250_dwlib.h | 1 +
4 files changed, 38 insertions(+), 11 deletions(-)
base-commit: 36844ea19656fb41278799ef3d5cd52120d89149
--
2.43.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 1/3] serial: 8250_dw: Keep the BUSY-safe divisor hook when DLF is present
2026-10-05 17:25 [PATCH 0/3] serial: 8250_dw: Allwinner A733 UART support Vinicius Pedrosa
@ 2026-10-05 17:25 ` Vinicius Pedrosa
2026-10-06 10:19 ` Ilpo Järvinen
2026-10-05 17:25 ` [PATCH 2/3] dt-bindings: serial: snps-dw-apb-uart: Add Allwinner A733 Vinicius Pedrosa
2026-10-05 17:25 ` [PATCH 3/3] serial: 8250_dw: Add Allwinner A733 UART Vinicius Pedrosa
2 siblings, 1 reply; 8+ messages in thread
From: Vinicius Pedrosa @ 2026-10-05 17:25 UTC (permalink / raw)
To: linux-serial
Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, andriy.shevchenko,
ilpo.jarvinen, devicetree, linux-kernel, linux-sunxi,
Enzo Adriano, stable
dw8250_probe() installs dw8250_set_divisor(), which brackets the
DLAB/DLL/DLH sequence with dw8250_idle_enter()/dw8250_idle_exit() so that
a BUSY UART cannot drop the divisor LCR write. dw8250_setup_port() runs
afterwards and replaces that hook with the dwlib one whenever it detects
a DLF register. The dwlib hook programs the divisor through
serial8250_do_set_divisor() without entering idle.
When termios changes while the UART is transmitting, the LCR|DLAB write
is ignored and dw8250_check_lcr() retries it between
dw8250_idle_enter() and dw8250_idle_exit(). dw8250_idle_exit() restores
IER while DLAB is still set, so the value lands in DLH. The real IER
keeps the zero written by dw8250_idle_enter(), and the port gets no
further TX or RX interrupts while up->ier says they're enabled.
Program DLF from dw8250_set_divisor() inside the idle section and keep
that hook after dw8250_setup_port().
Observed on an Allwinner A733 (Radxa Cubie A7Z): 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>
---
The A733 has no real DLF. Offset 0xc0 is an RS485 control register that
dw8250_setup_port() mistakes for one (patch 3), which is how this path
was reached. The new DLF write hasn't been tested on a UART with a real
DLF register.
drivers/tty/serial/8250/8250_dw.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 0ab558230370..ba414306c98a 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -189,6 +189,7 @@ static int dw8250_idle_enter(struct uart_port *p)
static void dw8250_set_divisor(struct uart_port *p, unsigned int baud,
unsigned int quot, unsigned int quot_frac)
{
+ struct dw8250_data *d = to_dw8250_data(p->private_data);
struct uart_8250_port *up = up_to_u8250p(p);
int ret;
@@ -196,6 +197,9 @@ static void dw8250_set_divisor(struct uart_port *p, unsigned int baud,
if (ret < 0)
return;
+ if (d->data.dlf_size)
+ dw8250_writel_ext(p, DW_UART_DLF, 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;
@@ -650,7 +654,6 @@ static int dw8250_probe(struct platform_device *pdev)
p->set_ldisc = dw8250_set_ldisc;
p->set_termios = dw8250_set_termios;
- p->set_divisor = dw8250_set_divisor;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
@@ -767,6 +770,13 @@ static int dw8250_probe(struct platform_device *pdev)
if (!data->skip_autocfg)
dw8250_setup_port(p);
+ /*
+ * dw8250_setup_port() installs the library divisor hook when it finds
+ * DLF. Keep the BUSY-safe hook instead: it also programs DLF, and it
+ * must own DLAB so that a BUSY UART never drops the divisor LCR write.
+ */
+ p->set_divisor = dw8250_set_divisor;
+
/* If we have a valid fifosize, try hooking up DMA */
if (p->fifosize) {
data->data.dma.rxconf.src_maxburst = p->fifosize / 4;
--
2.43.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 2/3] dt-bindings: serial: snps-dw-apb-uart: Add Allwinner A733
2026-10-05 17:25 [PATCH 0/3] serial: 8250_dw: Allwinner A733 UART support Vinicius Pedrosa
2026-10-05 17:25 ` [PATCH 1/3] serial: 8250_dw: Keep the BUSY-safe divisor hook when DLF is present Vinicius Pedrosa
@ 2026-10-05 17:25 ` Vinicius Pedrosa
2026-10-05 17:25 ` [PATCH 3/3] serial: 8250_dw: Add Allwinner A733 UART Vinicius Pedrosa
2 siblings, 0 replies; 8+ messages in thread
From: Vinicius Pedrosa @ 2026-10-05 17:25 UTC (permalink / raw)
To: linux-serial
Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, andriy.shevchenko,
ilpo.jarvinen, devicetree, linux-kernel, linux-sunxi,
Enzo Adriano
The Allwinner A733 UART is a DesignWare APB UART with two SoC-specific
differences. Its baud clock is the bus clock gate, which can't change
rate, and offset 0xc0 holds an RS485 control register instead of DLF.
Add a SoC-specific compatible, with "snps,dw-apb-uart" as the fallback.
Signed-off-by: Vinicius Pedrosa <vinicius.eduardo.pedrosa@gmail.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 86dc25919311..aa8cab5ba3d3 100644
--- a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml
+++ b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml
@@ -51,6 +51,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
--
2.43.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 3/3] serial: 8250_dw: Add Allwinner A733 UART
2026-10-05 17:25 [PATCH 0/3] serial: 8250_dw: Allwinner A733 UART support Vinicius Pedrosa
2026-10-05 17:25 ` [PATCH 1/3] serial: 8250_dw: Keep the BUSY-safe divisor hook when DLF is present Vinicius Pedrosa
2026-10-05 17:25 ` [PATCH 2/3] dt-bindings: serial: snps-dw-apb-uart: Add Allwinner A733 Vinicius Pedrosa
@ 2026-10-05 17:25 ` Vinicius Pedrosa
2026-10-06 9:46 ` Andre Przywara
2 siblings, 1 reply; 8+ messages in thread
From: Vinicius Pedrosa @ 2026-10-05 17:25 UTC (permalink / raw)
To: linux-serial
Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, andriy.shevchenko,
ilpo.jarvinen, devicetree, linux-kernel, linux-sunxi,
Enzo Adriano
The A733 UART is clocked from its bus clock gate, which can't change
rate. dw8250_set_termios() still gates it around a no-op clk_set_rate()
on every termios change. That stalls the character being shifted out
and corrupts it on the wire. Use the existing SKIP_SET_RATE quirk, as
other SoCs with a fixed UART clock do.
Offset 0xc0 is the RS485 control register on this SoC (A733 User
Manual, UART_485_CTL), not DLF. dw8250_setup_port() writes all ones
there, reads back a nonzero 9-bit value and takes it for a 9-bit DLF.
Every later divisor change then writes the fractional divisor into the
RS485 control register. Add a NO_DLF quirk so dwlib skips that probe.
Signed-off-by: Vinicius Pedrosa <vinicius.eduardo.pedrosa@gmail.com>
---
drivers/tty/serial/8250/8250_dw.c | 13 +++++++++++++
drivers/tty/serial/8250/8250_dwlib.c | 22 ++++++++++++----------
drivers/tty/serial/8250/8250_dwlib.h | 1 +
3 files changed, 26 insertions(+), 10 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index ba414306c98a..ea33aa644cda 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -52,6 +52,7 @@
#define DW_UART_QUIRK_CPR_VALUE BIT(5)
#define DW_UART_QUIRK_IER_KICK BIT(6)
#define DW_UART_QUIRK_SKIP_EMPTY_FIFO_READ BIT(7)
+#define DW_UART_QUIRK_NO_DLF BIT(8)
/*
* Number of consecutive IIR_NO_INT interrupts required to trigger interrupt
@@ -606,6 +607,8 @@ static void dw8250_quirks(struct uart_port *p, struct dw8250_data *data)
p->serial_out = dw8250_serial_out38x;
if (quirks & DW_UART_QUIRK_SKIP_SET_RATE)
p->set_termios = dw8250_do_set_termios;
+ if (quirks & DW_UART_QUIRK_NO_DLF)
+ data->data.no_dlf = true;
if (quirks & DW_UART_QUIRK_IS_DMA_FC) {
data->data.dma.txconf.device_fc = 1;
data->data.dma.rxconf.device_fc = 1;
@@ -892,6 +895,15 @@ static const struct dw8250_platform_data dw8250_skip_set_rate_data = {
.quirks = DW_UART_QUIRK_SKIP_SET_RATE,
};
+/*
+ * The baud clock is the bus clock gate, whose rate cannot change, and offset
+ * 0xc0 is the RS485 control register rather than DLF.
+ */
+static const struct dw8250_platform_data dw8250_sun60i_a733_data = {
+ .usr_reg = DW_UART_USR,
+ .quirks = DW_UART_QUIRK_SKIP_SET_RATE | DW_UART_QUIRK_NO_DLF,
+};
+
static const struct dw8250_platform_data dw8250_intc10ee = {
.usr_reg = DW_UART_USR,
.quirks = DW_UART_QUIRK_IER_KICK,
@@ -913,6 +925,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_sun60i_a733_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 },
diff --git a/drivers/tty/serial/8250/8250_dwlib.c b/drivers/tty/serial/8250/8250_dwlib.c
index 9bb02a4ab11f..9c6f3d926ad5 100644
--- a/drivers/tty/serial/8250/8250_dwlib.c
+++ b/drivers/tty/serial/8250/8250_dwlib.c
@@ -209,16 +209,18 @@ void dw8250_setup_port(struct uart_port *p)
}
up->capabilities |= UART_CAP_NOTEMT;
- /* Preserve value written by firmware or bootloader */
- old_dlf = dw8250_readl_ext(p, DW_UART_DLF);
- dw8250_writel_ext(p, DW_UART_DLF, ~0U);
- reg = dw8250_readl_ext(p, DW_UART_DLF);
- dw8250_writel_ext(p, DW_UART_DLF, old_dlf);
-
- if (reg) {
- pd->dlf_size = fls(reg);
- p->get_divisor = dw8250_get_divisor;
- p->set_divisor = dw8250_set_divisor;
+ if (!pd->no_dlf) {
+ /* Preserve value written by firmware or bootloader */
+ old_dlf = dw8250_readl_ext(p, DW_UART_DLF);
+ dw8250_writel_ext(p, DW_UART_DLF, ~0U);
+ reg = dw8250_readl_ext(p, DW_UART_DLF);
+ dw8250_writel_ext(p, DW_UART_DLF, old_dlf);
+
+ if (reg) {
+ pd->dlf_size = fls(reg);
+ p->get_divisor = dw8250_get_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 ee7a07fac0f6..ca0dfd6d056e 100644
--- a/drivers/tty/serial/8250/8250_dwlib.h
+++ b/drivers/tty/serial/8250/8250_dwlib.h
@@ -88,6 +88,7 @@ struct dw8250_port_data {
/* Hardware configuration */
u32 cpr_value;
u8 dlf_size;
+ bool no_dlf; /* Offset 0xc0 is not DLF */
/* RS485 variables */
bool hw_rs485_support;
--
2.43.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 3/3] serial: 8250_dw: Add Allwinner A733 UART
2026-10-05 17:25 ` [PATCH 3/3] serial: 8250_dw: Add Allwinner A733 UART Vinicius Pedrosa
@ 2026-10-06 9:46 ` Andre Przywara
2026-10-06 11:33 ` Vinicius Pedrosa
0 siblings, 1 reply; 8+ messages in thread
From: Andre Przywara @ 2026-10-06 9:46 UTC (permalink / raw)
To: Vinicius Pedrosa, linux-serial
Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, andriy.shevchenko,
ilpo.jarvinen, devicetree, linux-kernel, linux-sunxi,
Enzo Adriano
Hi Vinicius,
On 10/5/26 19:25, Vinicius Pedrosa wrote:
> The A733 UART is clocked from its bus clock gate, which can't change
> rate. dw8250_set_termios() still gates it around a no-op clk_set_rate()
> on every termios change. That stalls the character being shifted out
> and corrupts it on the wire. Use the existing SKIP_SET_RATE quirk, as
> other SoCs with a fixed UART clock do.
>
> Offset 0xc0 is the RS485 control register on this SoC (A733 User
> Manual, UART_485_CTL), not DLF. dw8250_setup_port() writes all ones
> there, reads back a nonzero 9-bit value and takes it for a 9-bit DLF.
> Every later divisor change then writes the fractional divisor into the
> RS485 control register. Add a NO_DLF quirk so dwlib skips that probe.
many thanks for digging into this, and superficially it looks like the
right thing to do. But looking into some manuals, it seems like
Allwinner added this RS485 support on registers +0xc0, 0xc4 and 0xc8
already a while ago: I find it in the H6, H616, A523 manuals, for
instance. And while register 0x50004c0 ignores writes on the H616, the
corresponding 0x25004c0 reacts on the A523, so at least that one is
already broken.
So as Sashiko mentioned, we would technically need to drop the fallback
compatible, and doing this also for the A523 would break all older
kernels, so that isn't a good option.
Now when I write 0xffffffff into +0xc0 on the A523, I read 0x9f back, so
there are zero bits in the middle, which doesn't match the expected
contiguous bitmask for the fractional divider bits.
So I was wondering if we should refine the DLF detection instead? Only
when the readback from 0xc0 returns some 2^n-1 value we assume DLF is
implemented?
Alternatively making DLF a (negative?) DT/ACPI property instead, and
keep the fallback compatible?
I guess this all depends a bit on what the snps,dw-apb-uart compatible
string really covers, and if Allwinner hacked^Wchanged the IP, by adding
their RS485 bits on top of on older Designware IP, or if this is a valid
configuration.
Cheers,
Andre
> Signed-off-by: Vinicius Pedrosa <vinicius.eduardo.pedrosa@gmail.com>
> ---
> drivers/tty/serial/8250/8250_dw.c | 13 +++++++++++++
> drivers/tty/serial/8250/8250_dwlib.c | 22 ++++++++++++----------
> drivers/tty/serial/8250/8250_dwlib.h | 1 +
> 3 files changed, 26 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
> index ba414306c98a..ea33aa644cda 100644
> --- a/drivers/tty/serial/8250/8250_dw.c
> +++ b/drivers/tty/serial/8250/8250_dw.c
> @@ -52,6 +52,7 @@
> #define DW_UART_QUIRK_CPR_VALUE BIT(5)
> #define DW_UART_QUIRK_IER_KICK BIT(6)
> #define DW_UART_QUIRK_SKIP_EMPTY_FIFO_READ BIT(7)
> +#define DW_UART_QUIRK_NO_DLF BIT(8)
>
> /*
> * Number of consecutive IIR_NO_INT interrupts required to trigger interrupt
> @@ -606,6 +607,8 @@ static void dw8250_quirks(struct uart_port *p, struct dw8250_data *data)
> p->serial_out = dw8250_serial_out38x;
> if (quirks & DW_UART_QUIRK_SKIP_SET_RATE)
> p->set_termios = dw8250_do_set_termios;
> + if (quirks & DW_UART_QUIRK_NO_DLF)
> + data->data.no_dlf = true;
> if (quirks & DW_UART_QUIRK_IS_DMA_FC) {
> data->data.dma.txconf.device_fc = 1;
> data->data.dma.rxconf.device_fc = 1;
> @@ -892,6 +895,15 @@ static const struct dw8250_platform_data dw8250_skip_set_rate_data = {
> .quirks = DW_UART_QUIRK_SKIP_SET_RATE,
> };
>
> +/*
> + * The baud clock is the bus clock gate, whose rate cannot change, and offset
> + * 0xc0 is the RS485 control register rather than DLF.
> + */
> +static const struct dw8250_platform_data dw8250_sun60i_a733_data = {
> + .usr_reg = DW_UART_USR,
> + .quirks = DW_UART_QUIRK_SKIP_SET_RATE | DW_UART_QUIRK_NO_DLF,
> +};
> +
> static const struct dw8250_platform_data dw8250_intc10ee = {
> .usr_reg = DW_UART_USR,
> .quirks = DW_UART_QUIRK_IER_KICK,
> @@ -913,6 +925,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_sun60i_a733_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 },
> diff --git a/drivers/tty/serial/8250/8250_dwlib.c b/drivers/tty/serial/8250/8250_dwlib.c
> index 9bb02a4ab11f..9c6f3d926ad5 100644
> --- a/drivers/tty/serial/8250/8250_dwlib.c
> +++ b/drivers/tty/serial/8250/8250_dwlib.c
> @@ -209,16 +209,18 @@ void dw8250_setup_port(struct uart_port *p)
> }
> up->capabilities |= UART_CAP_NOTEMT;
>
> - /* Preserve value written by firmware or bootloader */
> - old_dlf = dw8250_readl_ext(p, DW_UART_DLF);
> - dw8250_writel_ext(p, DW_UART_DLF, ~0U);
> - reg = dw8250_readl_ext(p, DW_UART_DLF);
> - dw8250_writel_ext(p, DW_UART_DLF, old_dlf);
> -
> - if (reg) {
> - pd->dlf_size = fls(reg);
> - p->get_divisor = dw8250_get_divisor;
> - p->set_divisor = dw8250_set_divisor;
> + if (!pd->no_dlf) {
> + /* Preserve value written by firmware or bootloader */
> + old_dlf = dw8250_readl_ext(p, DW_UART_DLF);
> + dw8250_writel_ext(p, DW_UART_DLF, ~0U);
> + reg = dw8250_readl_ext(p, DW_UART_DLF);
> + dw8250_writel_ext(p, DW_UART_DLF, old_dlf);
> +
> + if (reg) {
> + pd->dlf_size = fls(reg);
> + p->get_divisor = dw8250_get_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 ee7a07fac0f6..ca0dfd6d056e 100644
> --- a/drivers/tty/serial/8250/8250_dwlib.h
> +++ b/drivers/tty/serial/8250/8250_dwlib.h
> @@ -88,6 +88,7 @@ struct dw8250_port_data {
> /* Hardware configuration */
> u32 cpr_value;
> u8 dlf_size;
> + bool no_dlf; /* Offset 0xc0 is not DLF */
>
> /* RS485 variables */
> bool hw_rs485_support;
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/3] serial: 8250_dw: Keep the BUSY-safe divisor hook when DLF is present
2026-10-05 17:25 ` [PATCH 1/3] serial: 8250_dw: Keep the BUSY-safe divisor hook when DLF is present Vinicius Pedrosa
@ 2026-10-06 10:19 ` Ilpo Järvinen
2026-10-06 11:32 ` Vinicius Pedrosa
0 siblings, 1 reply; 8+ messages in thread
From: Ilpo Järvinen @ 2026-10-06 10:19 UTC (permalink / raw)
To: Vinicius Pedrosa
Cc: linux-serial, Greg Kroah-Hartman, Jiri Slaby, robh, krzk+dt,
conor+dt, Andy Shevchenko, devicetree, LKML, linux-sunxi,
Enzo Adriano, stable
On Mon, 5 Oct 2026, Vinicius Pedrosa wrote:
> dw8250_probe() installs dw8250_set_divisor(), which brackets the
> DLAB/DLL/DLH sequence with dw8250_idle_enter()/dw8250_idle_exit() so that
> a BUSY UART cannot drop the divisor LCR write. dw8250_setup_port() runs
> afterwards and replaces that hook with the dwlib one whenever it detects
> a DLF register. The dwlib hook programs the divisor through
> serial8250_do_set_divisor() without entering idle.
Yeah, non-dwlib code should own the hook if it sets one.
> When termios changes while the UART is transmitting, the LCR|DLAB write
> is ignored and dw8250_check_lcr() retries it between
> dw8250_idle_enter() and dw8250_idle_exit(). dw8250_idle_exit() restores
> IER while DLAB is still set, so the value lands in DLH. The real IER
> keeps the zero written by dw8250_idle_enter(), and the port gets no
> further TX or RX interrupts while up->ier says they're enabled.
>
> Program DLF from dw8250_set_divisor() inside the idle section and keep
> that hook after dw8250_setup_port().
>
> Observed on an Allwinner A733 (Radxa Cubie A7Z): 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>
> ---
> The A733 has no real DLF. Offset 0xc0 is an RS485 control register that
> dw8250_setup_port() mistakes for one (patch 3), which is how this path
> was reached. The new DLF write hasn't been tested on a UART with a real
> DLF register.
>
> drivers/tty/serial/8250/8250_dw.c | 12 +++++++++++-
> 1 file changed, 11 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
> index 0ab558230370..ba414306c98a 100644
> --- a/drivers/tty/serial/8250/8250_dw.c
> +++ b/drivers/tty/serial/8250/8250_dw.c
> @@ -189,6 +189,7 @@ static int dw8250_idle_enter(struct uart_port *p)
> static void dw8250_set_divisor(struct uart_port *p, unsigned int baud,
> unsigned int quot, unsigned int quot_frac)
> {
> + struct dw8250_data *d = to_dw8250_data(p->private_data);
> struct uart_8250_port *up = up_to_u8250p(p);
> int ret;
>
> @@ -196,6 +197,9 @@ static void dw8250_set_divisor(struct uart_port *p, unsigned int baud,
> if (ret < 0)
> return;
>
> + if (d->data.dlf_size)
> + dw8250_writel_ext(p, DW_UART_DLF, quot_frac);
It might be better to add __dw8250_set_divisor() to dwlib to keep the DLF
setting code in one place. If the dwlib side does d->data.dlf_size check,
8250_dw side doesn't need to care about DLF stuff but can just
unconditionally call __dw8250_set_divisor().
> +
> serial_port_out(p, UART_LCR, up->lcr | UART_LCR_DLAB);
> if (!(serial_port_in(p, UART_LCR) & UART_LCR_DLAB))
> goto idle_failed;
> @@ -650,7 +654,6 @@ static int dw8250_probe(struct platform_device *pdev)
>
> p->set_ldisc = dw8250_set_ldisc;
> p->set_termios = dw8250_set_termios;
> - p->set_divisor = dw8250_set_divisor;
>
> data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
> if (!data)
> @@ -767,6 +770,13 @@ static int dw8250_probe(struct platform_device *pdev)
> if (!data->skip_autocfg)
> dw8250_setup_port(p);
>
> + /*
> + * dw8250_setup_port() installs the library divisor hook when it finds
> + * DLF. Keep the BUSY-safe hook instead: it also programs DLF, and it
> + * must own DLAB so that a BUSY UART never drops the divisor LCR write.
> + */
> + p->set_divisor = dw8250_set_divisor;
> +
> /* If we have a valid fifosize, try hooking up DMA */
> if (p->fifosize) {
> data->data.dma.rxconf.src_maxburst = p->fifosize / 4;
>
It might be safer approach to NULL check .set_divisor in dwlib before
setting it as ordering requirements like this are easy to miss.
--
i.
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/3] serial: 8250_dw: Keep the BUSY-safe divisor hook when DLF is present
2026-10-06 10:19 ` Ilpo Järvinen
@ 2026-10-06 11:32 ` Vinicius Pedrosa
0 siblings, 0 replies; 8+ messages in thread
From: Vinicius Pedrosa @ 2026-10-06 11:32 UTC (permalink / raw)
To: Ilpo Järvinen
Cc: linux-serial, Greg Kroah-Hartman, Jiri Slaby, robh, krzk+dt,
conor+dt, Andy Shevchenko, devicetree, linux-kernel, linux-sunxi,
Enzo Adriano, stable
Hi Ilpo,
On Tue, 6 Oct 2026, Ilpo Järvinen wrote:
> It might be better to add __dw8250_set_divisor() to dwlib to keep the DLF
> setting code in one place.
[...]
> It might be safer approach to NULL check .set_divisor in dwlib before
> setting it as ordering requirements like this are easy to miss.
Agreed on both, I'll do it that way in v2. I reran patch 1 on its own
after the change and the console still survives the termios stress loop.
Thanks,
Vinicius
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 3/3] serial: 8250_dw: Add Allwinner A733 UART
2026-10-06 9:46 ` Andre Przywara
@ 2026-10-06 11:33 ` Vinicius Pedrosa
0 siblings, 0 replies; 8+ messages in thread
From: Vinicius Pedrosa @ 2026-10-06 11:33 UTC (permalink / raw)
To: Andre Przywara, linux-serial
Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, andriy.shevchenko,
ilpo.jarvinen, devicetree, linux-kernel, linux-sunxi,
Enzo Adriano
Hi Andre,
On 10/6/26 11:46, Andre Przywara wrote:
> So I was wondering if we should refine the DLF detection instead? Only
> when the readback from 0xc0 returns some 2^n-1 value we assume DLF is
> implemented?
Thanks, that's better than a per-SoC quirk. On the A7Z, writing all ones
to 0xc0 reads back 0x19f, so the same family as your 0x9f with bit 8 on
top. A 2^n - 1 check rejects it.
With the check on the board, dlf_size is 0 and 0xc0 is still 0 after the
termios stress loop. Without it, 0xc0 reads 0xb after boot. I don't have
a UART with a real DLF to try.
v2 puts the check in its own patch and keeps the fallback, with the A733
compatible only selecting SKIP_SET_RATE. On the fallback alone the same
loop leaves 28 garbage bytes in 3000 lines.
The probe itself still writes all ones to 0xc0 before restoring it. Would
you rather skip that with a DT property, or leave it?
Thanks,
Vinicius
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-10-06 11:33 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-05 17:25 [PATCH 0/3] serial: 8250_dw: Allwinner A733 UART support Vinicius Pedrosa
2026-10-05 17:25 ` [PATCH 1/3] serial: 8250_dw: Keep the BUSY-safe divisor hook when DLF is present Vinicius Pedrosa
2026-10-06 10:19 ` Ilpo Järvinen
2026-10-06 11:32 ` Vinicius Pedrosa
2026-10-05 17:25 ` [PATCH 2/3] dt-bindings: serial: snps-dw-apb-uart: Add Allwinner A733 Vinicius Pedrosa
2026-10-05 17:25 ` [PATCH 3/3] serial: 8250_dw: Add Allwinner A733 UART Vinicius Pedrosa
2026-10-06 9:46 ` Andre Przywara
2026-10-06 11:33 ` Vinicius Pedrosa
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®