[PATCH 2/2] serial: 8250_ingenic: Start the X1600 UART from 16 clocks a bit

From: Olivier Louvignes

Date: Sat Oct 24 2026 - 01:17:34 EST


The X1600 UART samples each bit with UMR clocks, adjusted per bit by
UACR, while the divisor the 8250 core computes assumes 16. The driver
never sets them, so the UART keeps what ran before it: on a HiBy R1 the
vendor kernel leaves UART0's UMR at 8, which by the register's meaning
doubles every rate set here (the line rate itself was not measured). The
Bluetooth chip on that UART then never answers: "command 0xfc18 tx
timeout" from hci_bcm.

Add an X1600 entry that writes UMR = 16 and UACR = 0 at probe. The
offsets come from Rockbox's X1600 register headers and match the UMR
read on the R1. The X1000, whose compatible the X1600's falls back to,
may have the same registers, but I could not check one, so it is left
alone.

Tested on a HiBy R1: UMR reads 16 and UACR 0 after probe, and the
BCM43430A1 on UART0 comes up at 1.5 Mbaud and streams A2DP audio.

Signed-off-by: Olivier Louvignes <olivier@xxxxxxxxx>
---
The test on the R1 ran with the same reset placed in the X1000 entry; the
R1's device tree lists "ingenic,x1600-uart" first, so it takes the same
path with this version. Build-tested with W=1 on tty-next.

drivers/tty/serial/8250/8250_ingenic.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)

diff --git a/drivers/tty/serial/8250/8250_ingenic.c b/drivers/tty/serial/8250/8250_ingenic.c
index 0cdadacd7019..40307189021b 100644
--- a/drivers/tty/serial/8250/8250_ingenic.c
+++ b/drivers/tty/serial/8250/8250_ingenic.c
@@ -25,6 +25,7 @@
struct ingenic_uart_config {
int tx_loadsz;
int fifosize;
+ bool has_umr;
};

struct ingenic_uart_data {
@@ -40,6 +41,10 @@ static const struct of_device_id of_match[];
#define UART_MCR_MDCE BIT(7)
#define UART_MCR_FCM BIT(6)

+/* Clocks a bit, 16 for the divisor the 8250 core computes, and their per-bit adjustment. */
+#define UART_UMR 9
+#define UART_UACR 10
+
static struct earlycon_device *early_device;

static uint8_t early_in(struct uart_port *port, int offset)
@@ -298,6 +303,12 @@ static int ingenic_uart_probe(struct platform_device *pdev)
}
uart.port.uartclk = clk_get_rate(data->clk_baud);

+ /* Whatever ran before may have left another count: 8 on a HiBy R1. */
+ if (cdata->has_umr) {
+ writel(16, uart.port.membase + (UART_UMR << uart.port.regshift));
+ writel(0, uart.port.membase + (UART_UACR << uart.port.regshift));
+ }
+
data->uport = serial8250_register_8250_port(&uart);
if (IS_ERR(data->uport)) {
err = PTR_ERR(data->uport);
@@ -344,6 +355,12 @@ static const struct ingenic_uart_config x1000_uart_config = {
.fifosize = 64,
};

+static const struct ingenic_uart_config x1600_uart_config = {
+ .tx_loadsz = 32,
+ .fifosize = 64,
+ .has_umr = true,
+};
+
static const struct of_device_id of_match[] = {
{ .compatible = "ingenic,jz4740-uart", .data = &jz4740_uart_config },
{ .compatible = "ingenic,jz4750-uart", .data = &jz4760_uart_config },
@@ -352,6 +369,7 @@ static const struct of_device_id of_match[] = {
{ .compatible = "ingenic,jz4775-uart", .data = &jz4760_uart_config },
{ .compatible = "ingenic,jz4780-uart", .data = &jz4780_uart_config },
{ .compatible = "ingenic,x1000-uart", .data = &x1000_uart_config },
+ { .compatible = "ingenic,x1600-uart", .data = &x1600_uart_config },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, of_match);
--
2.39.5