* [PATCH v2 1/3] dt-bindings: serial: snps-dw-apb-uart: add Allwinner H616
2026-10-23 21:19 [PATCH v2 0/3] serial: 8250_dw: describe the Allwinner H616 UART configuration James Hilliard
@ 2026-10-23 21:19 ` James Hilliard
2026-10-23 21:19 ` [PATCH v2 2/3] serial: 8250_dw: configure the Allwinner H616 UART James Hilliard
2026-10-23 21:19 ` [PATCH v2 3/3] arm64: dts: allwinner: h616: identify the UART integration James Hilliard
2 siblings, 0 replies; 5+ messages in thread
From: James Hilliard @ 2026-10-23 21:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andy Shevchenko, Ilpo Järvinen, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland
Cc: linux-kernel, linux-serial, devicetree, linux-arm-kernel,
linux-sunxi, James Hilliard
The Allwinner H616 UART has 256-byte FIFOs but does not implement the
DesignWare component parameter register used to discover their size.
Add a compatible to identify this integration and its fixed FIFO
configuration. Retain snps,dw-apb-uart as a fallback.
Signed-off-by: James Hilliard <james.hilliard1@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 e2ca126d58ba..fc1289073241 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,sun50i-h616-uart
- anlogic,dr1v90-uart
- brcm,bcm11351-dw-apb-uart
- brcm,bcm21664-dw-apb-uart
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v2 2/3] serial: 8250_dw: configure the Allwinner H616 UART
2026-10-23 21:19 [PATCH v2 0/3] serial: 8250_dw: describe the Allwinner H616 UART configuration James Hilliard
2026-10-23 21:19 ` [PATCH v2 1/3] dt-bindings: serial: snps-dw-apb-uart: add Allwinner H616 James Hilliard
@ 2026-10-23 21:19 ` James Hilliard
2026-10-24 10:54 ` Andy Shevchenko
2026-10-23 21:19 ` [PATCH v2 3/3] arm64: dts: allwinner: h616: identify the UART integration James Hilliard
2 siblings, 1 reply; 5+ messages in thread
From: James Hilliard @ 2026-10-23 21:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andy Shevchenko, Ilpo Järvinen, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland
Cc: linux-kernel, linux-serial, devicetree, linux-arm-kernel,
linux-sunxi, James Hilliard
H616 has 256-byte transmit and receive FIFOs, but its optional DesignWare
component parameter register reads as zero. Generic 16550 probing leaves
the port using the 16-byte FIFO configuration and its trigger-level table.
DMA attachment also depends on a FIFO size being available during probe,
so these ports do not reach the existing 8250 DMA path.
Keep PORT_16550A and supply the FIFO size, transmit load and capabilities
when matching the H616 platform data. Provide receive-trigger callbacks
for the documented thresholds of 1, 64, 128 and 254 bytes. The normal
16550A half-full trigger selects 128 bytes on this integration. Serialize
trigger changes with termios and hold a runtime PM reference while
programming FCR, preserving the FIFO contents.
Advertise automatic flow control so CRTSCTS enables MCR.AFCE. The
DesignWare termios path selects hardware CTS handling, so the capability
must be present for the corresponding hardware control to be enabled.
Skip optional-register probing for this fixed configuration. H616 has
debug-divisor and RS485 control registers at the offsets used to detect
DesignWare hardware RS485 and fractional-divisor support. Probing these
offsets writes unrelated registers and can select incompatible callbacks.
Keep the BUSY-safe integer divisor hook and software RS485 handling.
Use the existing SKIP_SET_RATE quirk to keep the UART clock enabled during
termios changes. Its clock is a gate on the shared APB2 source, rather than
an independent baud-rate generator.
Attach DMA from the fixed configuration before registering the port.
Configure single-byte RX and TX bursts because the peripheral request can
correspond to just one character. The existing startup path requests the
firmware-described channels and falls back to PIO when they are unavailable
or the port is a kernel console.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/tty/serial/8250/8250_dw.c | 87 +++++++++++++++++++++++++++++++++++----
1 file changed, 79 insertions(+), 8 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index eca02e8bd6d1..b9f5cb61f2b6 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -19,6 +19,7 @@
#include <linux/io.h>
#include <linux/lockdep.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
@@ -66,6 +67,11 @@ struct dw8250_platform_data {
unsigned int quirks;
};
+static const struct dw8250_platform_data dw8250_h616_data = {
+ .usr_reg = DW_UART_USR,
+ .quirks = DW_UART_QUIRK_SKIP_SET_RATE,
+};
+
struct dw8250_data {
struct dw8250_port_data data;
const struct dw8250_platform_data *pdata;
@@ -630,6 +636,66 @@ static void dw8250_shutdown(struct uart_port *port)
d->no_int_count = 0;
}
+static const u8 dw8250_h616_rxtrig_bytes[] = { 1, 64, 128, 254 };
+
+static int dw8250_h616_get_rxtrig(struct uart_port *p)
+{
+ struct uart_8250_port *up = up_to_u8250p(p);
+
+ guard(uart_port_lock_irqsave)(p);
+
+ return dw8250_h616_rxtrig_bytes[UART_FCR_R_TRIG_BITS(up->fcr)];
+}
+
+static int dw8250_h616_set_rxtrig(struct uart_port *p, unsigned char bytes)
+{
+ struct uart_8250_port *up = up_to_u8250p(p);
+ int i, ret;
+
+ /* Use the nearest lower threshold, as the generic 8250 implementation does. */
+ for (i = 1; i < ARRAY_SIZE(dw8250_h616_rxtrig_bytes); i++)
+ if (bytes < dw8250_h616_rxtrig_bytes[i])
+ break;
+
+ guard(mutex)(&p->state->port.mutex);
+
+ ret = pm_runtime_resume_and_get(p->dev);
+ if (ret < 0)
+ return ret;
+
+ scoped_guard(uart_port_lock_irqsave, p) {
+ up->fcr &= ~UART_FCR_TRIGGER_MASK;
+ up->fcr |= (i - 1) << UART_FCR_R_TRIG_SHIFT;
+ /* Changing the trigger alone preserves the FIFO contents. */
+ serial_port_out(p, UART_FCR, up->fcr);
+ }
+ pm_runtime_put(p->dev);
+
+ return 0;
+}
+
+static void dw8250_h616_setup(struct uart_8250_port *up, struct dw8250_data *data)
+{
+ struct uart_port *p = &up->port;
+
+ p->type = PORT_16550A;
+ p->flags |= UPF_FIXED_TYPE;
+ p->fifosize = 256;
+ up->tx_loadsz = 256;
+ up->capabilities = UART_CAP_FIFO | UART_CAP_NOTEMT | UART_CAP_AFE;
+ p->get_rxtrig = dw8250_h616_get_rxtrig;
+ p->set_rxtrig = dw8250_h616_set_rxtrig;
+ p->rs485_config = serial8250_em485_config;
+ p->rs485_supported = serial8250_em485_supported;
+ up->rs485_start_tx = serial8250_em485_start_tx;
+ up->rs485_stop_tx = serial8250_em485_stop_tx;
+
+ /* A peripheral request can correspond to just one character. */
+ data->data.dma.rxconf.src_maxburst = 1;
+ data->data.dma.txconf.dst_maxburst = 1;
+ up->dma = &data->data.dma;
+}
+
static int dw8250_probe(struct platform_device *pdev)
{
struct uart_8250_port uart = {}, *up = &uart;
@@ -765,14 +831,18 @@ static int dw8250_probe(struct platform_device *pdev)
dw8250_setup_dma_filter(p, data);
- if (!data->skip_autocfg)
- dw8250_setup_port(p);
-
- /* If we have a valid fifosize, try hooking up DMA */
- if (p->fifosize) {
- data->data.dma.rxconf.src_maxburst = p->fifosize / 4;
- data->data.dma.txconf.dst_maxburst = p->fifosize / 4;
- up->dma = &data->data.dma;
+ if (data->pdata == &dw8250_h616_data) {
+ dw8250_h616_setup(up, data);
+ } else {
+ if (!data->skip_autocfg)
+ dw8250_setup_port(p);
+
+ /* If we have a valid fifosize, try hooking up DMA */
+ if (p->fifosize) {
+ data->data.dma.rxconf.src_maxburst = p->fifosize / 4;
+ data->data.dma.txconf.dst_maxburst = p->fifosize / 4;
+ up->dma = &data->data.dma;
+ }
}
data->data.uport = serial8250_register_8250_port(up);
@@ -904,6 +974,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,sun50i-h616-uart", .data = &dw8250_h616_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 },
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH v2 2/3] serial: 8250_dw: configure the Allwinner H616 UART
2026-10-23 21:19 ` [PATCH v2 2/3] serial: 8250_dw: configure the Allwinner H616 UART James Hilliard
@ 2026-10-24 10:54 ` Andy Shevchenko
0 siblings, 0 replies; 5+ messages in thread
From: Andy Shevchenko @ 2026-10-24 10:54 UTC (permalink / raw)
To: James Hilliard
Cc: Greg Kroah-Hartman, Jiri Slaby, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ilpo Järvinen, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, linux-kernel, linux-serial, devicetree,
linux-arm-kernel, linux-sunxi
On Fri, Oct 23, 2026 at 03:19:28PM -0600, James Hilliard wrote:
> H616 has 256-byte transmit and receive FIFOs, but its optional DesignWare
> component parameter register reads as zero. Generic 16550 probing leaves
> the port using the 16-byte FIFO configuration and its trigger-level table.
> DMA attachment also depends on a FIFO size being available during probe,
> so these ports do not reach the existing 8250 DMA path.
>
> Keep PORT_16550A and supply the FIFO size, transmit load and capabilities
> when matching the H616 platform data. Provide receive-trigger callbacks
> for the documented thresholds of 1, 64, 128 and 254 bytes. The normal
> 16550A half-full trigger selects 128 bytes on this integration. Serialize
> trigger changes with termios and hold a runtime PM reference while
> programming FCR, preserving the FIFO contents.
>
> Advertise automatic flow control so CRTSCTS enables MCR.AFCE. The
> DesignWare termios path selects hardware CTS handling, so the capability
> must be present for the corresponding hardware control to be enabled.
>
> Skip optional-register probing for this fixed configuration. H616 has
> debug-divisor and RS485 control registers at the offsets used to detect
> DesignWare hardware RS485 and fractional-divisor support. Probing these
> offsets writes unrelated registers and can select incompatible callbacks.
> Keep the BUSY-safe integer divisor hook and software RS485 handling.
>
> Use the existing SKIP_SET_RATE quirk to keep the UART clock enabled during
> termios changes. Its clock is a gate on the shared APB2 source, rather than
> an independent baud-rate generator.
>
> Attach DMA from the fixed configuration before registering the port.
> Configure single-byte RX and TX bursts because the peripheral request can
> correspond to just one character. The existing startup path requests the
> firmware-described channels and falls back to PIO when they are unavailable
> or the port is a kernel console.
...
> +static int dw8250_h616_set_rxtrig(struct uart_port *p, unsigned char bytes)
> +{
> + struct uart_8250_port *up = up_to_u8250p(p);
> + int i, ret;
Why is 'i' signed?
> + /* Use the nearest lower threshold, as the generic 8250 implementation does. */
> + for (i = 1; i < ARRAY_SIZE(dw8250_h616_rxtrig_bytes); i++)
> + if (bytes < dw8250_h616_rxtrig_bytes[i])
> + break;
> +
> + guard(mutex)(&p->state->port.mutex);
> +
> + ret = pm_runtime_resume_and_get(p->dev);
> + if (ret < 0)
> + return ret;
Don't we have a guard()() for this?
> + scoped_guard(uart_port_lock_irqsave, p) {
> + up->fcr &= ~UART_FCR_TRIGGER_MASK;
> + up->fcr |= (i - 1) << UART_FCR_R_TRIG_SHIFT;
> + /* Changing the trigger alone preserves the FIFO contents. */
> + serial_port_out(p, UART_FCR, up->fcr);
> + }
> + pm_runtime_put(p->dev);
> +
> + return 0;
> +}
...
> static int dw8250_probe(struct platform_device *pdev)
> - if (!data->skip_autocfg)
> - dw8250_setup_port(p);
> -
> - /* If we have a valid fifosize, try hooking up DMA */
> - if (p->fifosize) {
> - data->data.dma.rxconf.src_maxburst = p->fifosize / 4;
> - data->data.dma.txconf.dst_maxburst = p->fifosize / 4;
> - up->dma = &data->data.dma;
> + if (data->pdata == &dw8250_h616_data) {
> + dw8250_h616_setup(up, data);
> + } else {
> + if (!data->skip_autocfg)
> + dw8250_setup_port(p);
> +
> + /* If we have a valid fifosize, try hooking up DMA */
> + if (p->fifosize) {
> + data->data.dma.rxconf.src_maxburst = p->fifosize / 4;
> + data->data.dma.txconf.dst_maxburst = p->fifosize / 4;
> + up->dma = &data->data.dma;
> + }
> }
This looks unscalable and too much churn. What you should do is to add
a .setup() callback member to struct dw8250_platform_data and assign that
to dw8250_setup_port(), and change the
if (!data->skip_autocfg)
dw8250_setup_port(p);
to
if (data->pdata->setup)
data->pdata->setup(...);
which effectively kills that skip_autocfg. This is done in a separate patch.
Then in your patch you won't need this churn.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2 3/3] arm64: dts: allwinner: h616: identify the UART integration
2026-10-23 21:19 [PATCH v2 0/3] serial: 8250_dw: describe the Allwinner H616 UART configuration James Hilliard
2026-10-23 21:19 ` [PATCH v2 1/3] dt-bindings: serial: snps-dw-apb-uart: add Allwinner H616 James Hilliard
2026-10-23 21:19 ` [PATCH v2 2/3] serial: 8250_dw: configure the Allwinner H616 UART James Hilliard
@ 2026-10-23 21:19 ` James Hilliard
2 siblings, 0 replies; 5+ messages in thread
From: James Hilliard @ 2026-10-23 21:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andy Shevchenko, Ilpo Järvinen, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland
Cc: linux-kernel, linux-serial, devicetree, linux-arm-kernel,
linux-sunxi, James Hilliard
Use the H616 UART compatible for all six controllers so the driver can
select their fixed 256-byte FIFO configuration and register layout.
Retain the generic DesignWare compatible as a fallback for older kernels.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
index c6b3a3f8359a..b4b2f85726e0 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
@@ -550,7 +550,7 @@ mmc2: mmc@4022000 {
};
uart0: serial@5000000 {
- compatible = "snps,dw-apb-uart";
+ compatible = "allwinner,sun50i-h616-uart", "snps,dw-apb-uart";
reg = <0x05000000 0x400>;
interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>;
reg-shift = <2>;
@@ -563,7 +563,7 @@ uart0: serial@5000000 {
};
uart1: serial@5000400 {
- compatible = "snps,dw-apb-uart";
+ compatible = "allwinner,sun50i-h616-uart", "snps,dw-apb-uart";
reg = <0x05000400 0x400>;
interrupts = <GIC_SPI 1 IRQ_TYPE_LEVEL_HIGH>;
reg-shift = <2>;
@@ -576,7 +576,7 @@ uart1: serial@5000400 {
};
uart2: serial@5000800 {
- compatible = "snps,dw-apb-uart";
+ compatible = "allwinner,sun50i-h616-uart", "snps,dw-apb-uart";
reg = <0x05000800 0x400>;
interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;
reg-shift = <2>;
@@ -589,7 +589,7 @@ uart2: serial@5000800 {
};
uart3: serial@5000c00 {
- compatible = "snps,dw-apb-uart";
+ compatible = "allwinner,sun50i-h616-uart", "snps,dw-apb-uart";
reg = <0x05000c00 0x400>;
interrupts = <GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>;
reg-shift = <2>;
@@ -602,7 +602,7 @@ uart3: serial@5000c00 {
};
uart4: serial@5001000 {
- compatible = "snps,dw-apb-uart";
+ compatible = "allwinner,sun50i-h616-uart", "snps,dw-apb-uart";
reg = <0x05001000 0x400>;
interrupts = <GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>;
reg-shift = <2>;
@@ -615,7 +615,7 @@ uart4: serial@5001000 {
};
uart5: serial@5001400 {
- compatible = "snps,dw-apb-uart";
+ compatible = "allwinner,sun50i-h616-uart", "snps,dw-apb-uart";
reg = <0x05001400 0x400>;
interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
reg-shift = <2>;
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread