From 43c2b86ff633c34831c8430925ba73d7c20da1ad Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Wed, 28 Jan 2026 11:28:54 +0000 Subject: [PATCH 01/35] tty: serial: samsung_tty: avoid dev_dbg deadlock commit a05025d0ce72 ("tty: serial: samsung_tty: use standard debugging macros") changed the debug prints to dev_dbg, which can result in deadlocks: s3c24xx_serial_set_termios can be called with the port lock, and then calls dev_dbg, which needs the console mutex. At the same time, s3c24xx_serial_console_write can be called with the console lock (e.g., inside console_unlock), and needs the port lock. To avoid this, move one dev_dbg call and just delete the other. Signed-off-by: Alyssa Milburn Link: https://patch.msgid.link/aXny9km6N1v9eoXU@zall.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/samsung_tty.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c index c1fabad6ba1f..e27806bf2cf3 100644 --- a/drivers/tty/serial/samsung_tty.c +++ b/drivers/tty/serial/samsung_tty.c @@ -1562,12 +1562,12 @@ static void s3c24xx_serial_set_termios(struct uart_port *port, ulcon |= S3C2410_LCON_PNONE; } - uart_port_lock_irqsave(port, &flags); - dev_dbg(port->dev, "setting ulcon to %08x, brddiv to %d, udivslot %08x\n", ulcon, quot, udivslot); + uart_port_lock_irqsave(port, &flags); + wr_regl(port, S3C2410_ULCON, ulcon); wr_regl(port, S3C2410_UBRDIV, quot); @@ -1587,12 +1587,6 @@ static void s3c24xx_serial_set_termios(struct uart_port *port, if (ourport->info->has_divslot) wr_regl(port, S3C2443_DIVSLOT, udivslot); - dev_dbg(port->dev, - "uart: ulcon = 0x%08x, ucon = 0x%08x, ufcon = 0x%08x\n", - rd_regl(port, S3C2410_ULCON), - rd_regl(port, S3C2410_UCON), - rd_regl(port, S3C2410_UFCON)); - /* * Update the per-port timeout. */ From 7885af04df6e4efd72910200c1bfc079d61202e4 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 28 Jan 2026 15:27:26 +0100 Subject: [PATCH 02/35] serial: 8250_port: Drop duplicate NULL check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit serial8250_release_dma() is NULL-aware, no need to check this in the caller. While at it, make sure DMA won't be used again, by NULLifying the pointer. Signed-off-by: Andy Shevchenko Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20260128142726.128175-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_port.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index cc94af2d578a..c05a6df8d6b4 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -2364,8 +2364,8 @@ void serial8250_do_shutdown(struct uart_port *port) synchronize_irq(port->irq); - if (up->dma) - serial8250_release_dma(up); + serial8250_release_dma(up); + up->dma = NULL; scoped_guard(uart_port_lock_irqsave, port) { if (port->flags & UPF_FOURPORT) { From f2a880e802ad12d1e38039d1334fb1475d0f5241 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 29 Jan 2026 23:29:37 -0800 Subject: [PATCH 03/35] tty: hvc_iucv: fix off-by-one in number of supported devices MAX_HVC_IUCV_LINES == HVC_ALLOC_TTY_ADAPTERS == 8. This is the number of entries in: static struct hvc_iucv_private *hvc_iucv_table[MAX_HVC_IUCV_LINES]; Sometimes hvc_iucv_table[] is limited by: (a) if (num > hvc_iucv_devices) // for error detection or (b) for (i = 0; i < hvc_iucv_devices; i++) // in 2 places (so these 2 don't agree; second one appears to be correct to me.) hvc_iucv_devices can be 0..8. This is a counter. (c) if (hvc_iucv_devices > MAX_HVC_IUCV_LINES) If hvc_iucv_devices == 8, (a) allows the code to access hvc_iucv_table[8]. Oops. Fixes: 44a01d5ba8a4 ("[S390] s390/hvc_console: z/VM IUCV hypervisor console support") Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260130072939.1535869-1-rdunlap@infradead.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/hvc/hvc_iucv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/hvc/hvc_iucv.c b/drivers/tty/hvc/hvc_iucv.c index 1dcdb9e99bd8..37db8a3e5158 100644 --- a/drivers/tty/hvc/hvc_iucv.c +++ b/drivers/tty/hvc/hvc_iucv.c @@ -130,7 +130,7 @@ static struct iucv_handler hvc_iucv_handler = { */ static struct hvc_iucv_private *hvc_iucv_get_private(uint32_t num) { - if (num > hvc_iucv_devices) + if (num >= hvc_iucv_devices) return NULL; return hvc_iucv_table[num]; } From c670267ff50d5f9beb486f0203cdede580a99ae3 Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Fri, 6 Feb 2026 14:20:03 +0800 Subject: [PATCH 04/35] tty: constify tty_ldisc_ops tty_ldisc_ops is not modified once registered, so make it const. Signed-off-by: Qingfang Deng Link: https://patch.msgid.link/20260206062004.1273890-1-dqfext@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_ldisc.c | 16 ++++++++-------- include/linux/tty_ldisc.h | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/tty/tty_ldisc.c b/drivers/tty/tty_ldisc.c index 888f2f8f9481..27fe8236f662 100644 --- a/drivers/tty/tty_ldisc.c +++ b/drivers/tty/tty_ldisc.c @@ -44,7 +44,7 @@ enum { static DEFINE_RAW_SPINLOCK(tty_ldiscs_lock); /* Line disc dispatch table */ -static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS]; +static const struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS]; /** * tty_register_ldisc - install a line discipline @@ -55,7 +55,7 @@ static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS]; * * Locking: takes %tty_ldiscs_lock to guard against ldisc races */ -int tty_register_ldisc(struct tty_ldisc_ops *new_ldisc) +int tty_register_ldisc(const struct tty_ldisc_ops *new_ldisc) { unsigned long flags; @@ -80,7 +80,7 @@ EXPORT_SYMBOL(tty_register_ldisc); * Locking: takes %tty_ldiscs_lock to guard against ldisc races */ -void tty_unregister_ldisc(struct tty_ldisc_ops *ldisc) +void tty_unregister_ldisc(const struct tty_ldisc_ops *ldisc) { unsigned long flags; @@ -90,10 +90,10 @@ void tty_unregister_ldisc(struct tty_ldisc_ops *ldisc) } EXPORT_SYMBOL(tty_unregister_ldisc); -static struct tty_ldisc_ops *get_ldops(int disc) +static const struct tty_ldisc_ops *get_ldops(int disc) { unsigned long flags; - struct tty_ldisc_ops *ldops, *ret; + const struct tty_ldisc_ops *ldops, *ret; raw_spin_lock_irqsave(&tty_ldiscs_lock, flags); ret = ERR_PTR(-EINVAL); @@ -107,7 +107,7 @@ static struct tty_ldisc_ops *get_ldops(int disc) return ret; } -static void put_ldops(struct tty_ldisc_ops *ldops) +static void put_ldops(const struct tty_ldisc_ops *ldops) { unsigned long flags; @@ -139,7 +139,7 @@ int tty_ldisc_autoload = IS_BUILTIN(CONFIG_LDISC_AUTOLOAD); static struct tty_ldisc *tty_ldisc_get(struct tty_struct *tty, int disc) { struct tty_ldisc *ld; - struct tty_ldisc_ops *ldops; + const struct tty_ldisc_ops *ldops; if (disc < N_TTY || disc >= NR_LDISCS) return ERR_PTR(-EINVAL); @@ -202,7 +202,7 @@ static void tty_ldiscs_seq_stop(struct seq_file *m, void *v) static int tty_ldiscs_seq_show(struct seq_file *m, void *v) { int i = *(loff_t *)v; - struct tty_ldisc_ops *ldops; + const struct tty_ldisc_ops *ldops; ldops = get_ldops(i); if (IS_ERR(ldops)) diff --git a/include/linux/tty_ldisc.h b/include/linux/tty_ldisc.h index c5cccc3fc1e8..d227a58e3e49 100644 --- a/include/linux/tty_ldisc.h +++ b/include/linux/tty_ldisc.h @@ -266,7 +266,7 @@ struct tty_ldisc_ops { }; struct tty_ldisc { - struct tty_ldisc_ops *ops; + const struct tty_ldisc_ops *ops; struct tty_struct *tty; }; @@ -281,8 +281,8 @@ struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *); void tty_ldisc_flush(struct tty_struct *tty); -int tty_register_ldisc(struct tty_ldisc_ops *new_ldisc); -void tty_unregister_ldisc(struct tty_ldisc_ops *ldisc); +int tty_register_ldisc(const struct tty_ldisc_ops *new_ldisc); +void tty_unregister_ldisc(const struct tty_ldisc_ops *ldisc); int tty_set_ldisc(struct tty_struct *tty, int disc); #endif /* _LINUX_TTY_LDISC_H */ From cd5e64c0bca25be6d1b779858a2d10aebc4976a3 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Mon, 9 Mar 2026 21:06:06 -0300 Subject: [PATCH 05/35] dt-bindings: serial: snps-dw-apb-uart: Add RV1103B compatible The RV1103B UART is compatible with the existing DesignWare APB UART binding. Add the rockchip,rv1103b-uart compatible string. Signed-off-by: Fabio Estevam Acked-by: Krzysztof Kozlowski Reviewed-by: Heiko Stuebner Link: https://patch.msgid.link/20260310000606.415206-1-festevam@gmail.com Signed-off-by: Greg Kroah-Hartman --- 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 6efe43089a74..685c1eceb782 100644 --- a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml +++ b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml @@ -71,6 +71,7 @@ properties: - rockchip,rk3568-uart - rockchip,rk3576-uart - rockchip,rk3588-uart + - rockchip,rv1103b-uart - rockchip,rv1108-uart - rockchip,rv1126-uart - sophgo,sg2044-uart From cc8de922bb8e3f4349e355de72bf2b3ef840c430 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Tue, 10 Feb 2026 12:50:59 +0000 Subject: [PATCH 06/35] serial: amba-pl011: Enable UART in earlycon setup Currently the PL011 driver only enables the UART (by setting UARTEN in REG_CR) in pl011_startup(), so if it is used for earlycon it is relying on the bootrom/firmware having left the UART enabled. There's no particular reason not to actively enable the UART before using it for earlycon, and the earlycon handling for e.g. the 8250 UART sets up the UART in its setup function, so follow that in the PL011. This allows use of earlycon with a UART that the firmware hasn't already been using for its own output, but the main motivation is that QEMU will otherwise log a message complaining that the guest is trying to write to a UART it never enabled. Signed-off-by: Peter Maydell Acked-by: Arnd Bergmann Link: https://patch.msgid.link/20260210125100.223138-1-peter.maydell@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/amba-pl011.c | 35 ++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 7f17d288c807..462a8c380059 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -2700,6 +2700,37 @@ static int pl011_early_read(struct console *con, char *s, unsigned int n) */ static int __init pl011_early_console_setup(struct earlycon_device *device, const char *opt) +{ + unsigned int cr; + + if (!device->port.membase) + return -ENODEV; + + device->con->write = pl011_early_write; + device->con->read = pl011_early_read; + + if (device->port.iotype == UPIO_MEM32) + cr = readl(device->port.membase + UART011_CR); + else + cr = readw(device->port.membase + UART011_CR); + cr &= UART011_CR_RTS | UART011_CR_DTR; + cr |= UART01x_CR_UARTEN | UART011_CR_RXE | UART011_CR_TXE; + if (device->port.iotype == UPIO_MEM32) + writel(cr, device->port.membase + UART011_CR); + else + writew(cr, device->port.membase + UART011_CR); + + return 0; +} + +OF_EARLYCON_DECLARE(pl011, "arm,pl011", pl011_early_console_setup); + +/* + * The SBSA UART has no defined control register and is assumed to + * be pre-enabled by firmware, so we do not write to UART011_CR. + */ +static int __init sbsa_uart_early_console_setup(struct earlycon_device *device, + const char *opt) { if (!device->port.membase) return -ENODEV; @@ -2710,9 +2741,7 @@ static int __init pl011_early_console_setup(struct earlycon_device *device, return 0; } -OF_EARLYCON_DECLARE(pl011, "arm,pl011", pl011_early_console_setup); - -OF_EARLYCON_DECLARE(pl011, "arm,sbsa-uart", pl011_early_console_setup); +OF_EARLYCON_DECLARE(pl011, "arm,sbsa-uart", sbsa_uart_early_console_setup); /* * On Qualcomm Datacenter Technologies QDF2400 SOCs affected by From 42157639ddc797053e0f16e6fe0f6b64034fe559 Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Wed, 25 Feb 2026 12:29:11 +0530 Subject: [PATCH 07/35] serial: amba-pl011: Introduce skip_ibrd_fbrd vendor flag The NVIDIA Tegra264 UART has a broken fractional baud rate divisor register. Using IBRD and FBRD may cause the baud rate to fall outside the required tolerance. Introduce the skip_ibrd_fbrd vendor flag to skip IBRD/FBRD programming. When set, the baud rate is derived directly from the UART clock rate using a fixed divisor. Signed-off-by: Kartik Rajput Link: https://patch.msgid.link/20260225065915.341522-2-kkartik@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/amba-pl011.c | 53 +++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 462a8c380059..b6d881eb87cc 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -114,6 +114,7 @@ struct vendor_data { bool cts_event_workaround; bool always_enabled; bool fixed_options; + bool skip_ibrd_fbrd; unsigned int (*get_fifosize)(struct amba_device *dev); }; @@ -2115,11 +2116,6 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios, uap->dmarx.poll_rate = DIV_ROUND_UP(10000000, baud); #endif - if (baud > port->uartclk / 16) - quot = DIV_ROUND_CLOSEST(port->uartclk * 8, baud); - else - quot = DIV_ROUND_CLOSEST(port->uartclk * 4, baud); - switch (termios->c_cflag & CSIZE) { case CS5: lcr_h = UART01x_LCRH_WLEN_5; @@ -2190,21 +2186,28 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios, old_cr &= ~ST_UART011_CR_OVSFACT; } - /* - * Workaround for the ST Micro oversampling variants to - * increase the bitrate slightly, by lowering the divisor, - * to avoid delayed sampling of start bit at high speeds, - * else we see data corruption. - */ - if (uap->vendor->oversampling) { - if (baud >= 3000000 && baud < 3250000 && quot > 1) - quot -= 1; - else if (baud > 3250000 && quot > 2) - quot -= 2; + if (!uap->vendor->skip_ibrd_fbrd) { + if (baud > port->uartclk / 16) + quot = DIV_ROUND_CLOSEST(port->uartclk * 8, baud); + else + quot = DIV_ROUND_CLOSEST(port->uartclk * 4, baud); + + /* + * Workaround for the ST Micro oversampling variants to + * increase the bitrate slightly, by lowering the divisor, + * to avoid delayed sampling of start bit at high speeds, + * else we see data corruption. + */ + if (uap->vendor->oversampling) { + if (baud >= 3000000 && baud < 3250000 && quot > 1) + quot -= 1; + else if (baud > 3250000 && quot > 2) + quot -= 2; + } + /* Set baud rate */ + pl011_write(quot & 0x3f, uap, REG_FBRD); + pl011_write(quot >> 6, uap, REG_IBRD); } - /* Set baud rate */ - pl011_write(quot & 0x3f, uap, REG_FBRD); - pl011_write(quot >> 6, uap, REG_IBRD); /* * ----------v----------v----------v----------v----- @@ -2374,6 +2377,7 @@ static void pl011_console_get_options(struct uart_amba_port *uap, int *baud, int *parity, int *bits) { unsigned int lcr_h, ibrd, fbrd; + unsigned int clkdiv; if (!(pl011_read(uap, REG_CR) & UART01x_CR_UARTEN)) return; @@ -2393,10 +2397,15 @@ static void pl011_console_get_options(struct uart_amba_port *uap, int *baud, else *bits = 8; - ibrd = pl011_read(uap, REG_IBRD); - fbrd = pl011_read(uap, REG_FBRD); + if (uap->vendor->skip_ibrd_fbrd) { + clkdiv = 64; + } else { + ibrd = pl011_read(uap, REG_IBRD); + fbrd = pl011_read(uap, REG_FBRD); + clkdiv = 64 * ibrd + fbrd; + } - *baud = uap->port.uartclk * 4 / (64 * ibrd + fbrd); + *baud = uap->port.uartclk * 4 / clkdiv; if (uap->vendor->oversampling && (pl011_read(uap, REG_CR) & ST_UART011_CR_OVSFACT)) From 87df45b4a83f13952958e9916af1b2dd56d4cfc7 Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Wed, 25 Feb 2026 12:29:12 +0530 Subject: [PATCH 08/35] serial: amba-pl011: Introduce set_uartclk_rate vendor flag The NVIDIA Tegra264 UART relies on configuring the UART clock rate directly to program the desired baud rate. Introduce the set_uartclk_rate vendor flag. When set, the driver uses clk_set_rate() to program the UART clock to the desired baud rate and clk_round_rate() to determine the maximum supported baud rate. Signed-off-by: Kartik Rajput Link: https://patch.msgid.link/20260225065915.341522-3-kkartik@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/amba-pl011.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index b6d881eb87cc..e12facb2a16a 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -115,6 +115,7 @@ struct vendor_data { bool always_enabled; bool fixed_options; bool skip_ibrd_fbrd; + bool set_uartclk_rate; unsigned int (*get_fifosize)(struct amba_device *dev); }; @@ -2096,6 +2097,7 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios, unsigned int lcr_h, old_cr; unsigned long flags; unsigned int baud, quot, clkdiv; + unsigned int max_baud; unsigned int bits; if (uap->vendor->oversampling) @@ -2103,11 +2105,34 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios, else clkdiv = 16; + max_baud = port->uartclk / clkdiv; + + if (uap->vendor->set_uartclk_rate) { + long max_clkrate = clk_round_rate(uap->clk, UINT_MAX); + + /* + * Clock is reprogrammable - determine max baud from the clock's + * maximum rate, not the current uartclk. + */ + if (max_clkrate > 0) + max_baud = max_clkrate / clkdiv; + } + /* * Ask the core to calculate the divisor for us. */ - baud = uart_get_baud_rate(port, termios, old, 0, - port->uartclk / clkdiv); + baud = uart_get_baud_rate(port, termios, old, 0, max_baud); + + if (uap->vendor->set_uartclk_rate) { + int err; + + err = clk_set_rate(uap->clk, baud * clkdiv); + if (err) { + dev_err(port->dev, "Failed to set clock rate: %d\n", err); + return; + } + } + #ifdef CONFIG_DMA_ENGINE /* * Adjust RX DMA polling rate with baud rate if not specified. From a2abd18e316ee2442631f0a60896b49dd8e9a80c Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Wed, 25 Feb 2026 12:29:13 +0530 Subject: [PATCH 09/35] serial: amba-pl011: Add Tegra264 UART support Add support for the NVIDIA Tegra264 UART controller, which is derived from the AMBA PL011 design. On Tegra264, the fractional baud rate divisor (FBRD) register is broken. Using IBRD alone may not achieve the required baud rate tolerance. Enable the skip_ibrd_fbrd and set_uartclk_rate flags for the NVIDIA variant. Signed-off-by: Kartik Rajput Link: https://patch.msgid.link/20260225065915.341522-4-kkartik@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/amba-pl011.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index e12facb2a16a..b604274c1791 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -218,6 +218,28 @@ static struct vendor_data vendor_st = { .get_fifosize = get_fifosize_st, }; +static unsigned int get_fifosize_nvidia(struct amba_device *dev) +{ + return 32; +} + +static struct vendor_data vendor_nvidia = { + .reg_offset = pl011_std_offsets, + .ifls = UART011_IFLS_RX4_8 | UART011_IFLS_TX4_8, + .fr_busy = UART01x_FR_BUSY, + .fr_dsr = UART01x_FR_DSR, + .fr_cts = UART01x_FR_CTS, + .fr_ri = UART011_FR_RI, + .oversampling = false, + .dma_threshold = false, + .cts_event_workaround = false, + .always_enabled = false, + .fixed_options = false, + .skip_ibrd_fbrd = true, + .set_uartclk_rate = true, + .get_fifosize = get_fifosize_nvidia, +}; + /* Deals with DMA transactions */ struct pl011_dmabuf { @@ -3144,6 +3166,11 @@ static const struct amba_id pl011_ids[] = { .mask = 0x00ffffff, .data = &vendor_st, }, + { + .id = 0x0006b011, + .mask = 0x000fffff, + .data = &vendor_nvidia, + }, { 0, 0 }, }; From d925538446d3f80e03a91a9ef7da34104a75df4d Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Wed, 25 Feb 2026 12:29:14 +0530 Subject: [PATCH 10/35] serial: amba-pl011: Respect DMA controller's copy_align requirement Some DMA controllers require transfer lengths to be aligned to a specific boundary. For example, the Tegra GPC DMA requires 4-byte (word) aligned transfers and will reject unaligned lengths. Align the TX DMA buffer length down to the DMA controller's copy_align boundary before submitting the transfer. Any remaining unaligned bytes will be transmitted via PIO on subsequent calls, which is the existing fallback behavior when DMA is not used. Signed-off-by: Kartik Rajput Link: https://patch.msgid.link/20260225065915.341522-5-kkartik@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/amba-pl011.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index b604274c1791..028e37ad8d79 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -649,6 +649,15 @@ static int pl011_dma_tx_refill(struct uart_amba_port *uap) count = PL011_DMA_BUFFER_SIZE; count = kfifo_out_peek(&tport->xmit_fifo, dmatx->buf, count); + + /* + * Align the TX buffer length to the DMA controller's copy_align + * requirements. Some DMA controllers (e.g., Tegra GPC DMA) require + * word-aligned transfers. Unaligned bytes will be sent via PIO. + */ + if (chan->device->copy_align) + count = ALIGN_DOWN(count, 1 << chan->device->copy_align); + dmatx->len = count; dmatx->dma = dma_map_single(dma_dev->dev, dmatx->buf, count, DMA_TO_DEVICE); From 2dccde6f5f2ff2726236fd69a1be813629c5512f Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Mon, 9 Mar 2026 12:23:21 +0000 Subject: [PATCH 11/35] serial: remove drivers for espressif esp32 These drivers were added about 3 years ago, and depend on the XTENSA_PLATFORM_ESP32 config option which has never existed, so no device can actually use them. They can only be compiled with COMPILE_TEST. In a previous conversation [1], Greg suggested removing the drivers, and Max, the original submitter of the drivers, agreed due to a lack of foreseeable development. Link: https://lore.kernel.org/all/20260308131412.1102749-1-julianbraha@gmail.com/ [1] Signed-off-by: Julian Braha Link: https://patch.msgid.link/20260309122321.1528622-1-julianbraha@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/Kconfig | 26 -- drivers/tty/serial/Makefile | 2 - drivers/tty/serial/esp32_acm.c | 459 ------------------- drivers/tty/serial/esp32_uart.c | 779 -------------------------------- 4 files changed, 1266 deletions(-) delete mode 100644 drivers/tty/serial/esp32_acm.c delete mode 100644 drivers/tty/serial/esp32_uart.c diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index f86775cfdcc9..686e7fb073b8 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1593,32 +1593,6 @@ config SERIAL_NUVOTON_MA35D1_CONSOLE but you can alter that using a kernel command line option such as "console=ttyNVTx". -config SERIAL_ESP32 - tristate "Espressif ESP32 UART support" - depends on XTENSA_PLATFORM_ESP32 || (COMPILE_TEST && OF) - select SERIAL_CORE - select SERIAL_CORE_CONSOLE - select SERIAL_EARLYCON - help - Driver for the UART controllers of the Espressif ESP32xx SoCs. - When earlycon option is enabled the following kernel command line - snippets may be used: - earlycon=esp32s3uart,mmio32,0x60000000,115200n8,40000000 - earlycon=esp32uart,mmio32,0x3ff40000,115200n8 - -config SERIAL_ESP32_ACM - tristate "Espressif ESP32 USB ACM gadget support" - depends on XTENSA_PLATFORM_ESP32 || (COMPILE_TEST && OF) - select SERIAL_CORE - select SERIAL_CORE_CONSOLE - select SERIAL_EARLYCON - help - Driver for the CDC ACM gadget controller of the Espressif ESP32S3 - SoCs that share separate USB controller with the JTAG adapter. - When earlycon option is enabled the following kernel command line - snippet may be used: - earlycon=esp32s3acm,mmio32,0x60038000 - endmenu config SERIAL_MCTRL_GPIO diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index a2ccbc508ec5..bba7b21a4a1d 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -37,8 +37,6 @@ obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o obj-$(CONFIG_SERIAL_CPM) += cpm_uart.o obj-$(CONFIG_SERIAL_CONEXANT_DIGICOLOR) += digicolor-usart.o obj-$(CONFIG_SERIAL_DZ) += dz.o -obj-$(CONFIG_SERIAL_ESP32) += esp32_uart.o -obj-$(CONFIG_SERIAL_ESP32_ACM) += esp32_acm.o obj-$(CONFIG_SERIAL_FSL_LINFLEXUART) += fsl_linflexuart.o obj-$(CONFIG_SERIAL_FSL_LPUART) += fsl_lpuart.o obj-$(CONFIG_SERIAL_ICOM) += icom.o diff --git a/drivers/tty/serial/esp32_acm.c b/drivers/tty/serial/esp32_acm.c deleted file mode 100644 index bb7cc65427f0..000000000000 --- a/drivers/tty/serial/esp32_acm.c +++ /dev/null @@ -1,459 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DRIVER_NAME "esp32s3-acm" -#define DEV_NAME "ttyGS" -#define UART_NR 4 - -#define ESP32S3_ACM_TX_FIFO_SIZE 64 - -#define USB_SERIAL_JTAG_EP1_REG 0x00 -#define USB_SERIAL_JTAG_EP1_CONF_REG 0x04 -#define USB_SERIAL_JTAG_WR_DONE BIT(0) -#define USB_SERIAL_JTAG_SERIAL_IN_EP_DATA_FREE BIT(1) -#define USB_SERIAL_JTAG_INT_ST_REG 0x0c -#define USB_SERIAL_JTAG_SERIAL_OUT_RECV_PKT_INT_ST BIT(2) -#define USB_SERIAL_JTAG_SERIAL_IN_EMPTY_INT_ST BIT(3) -#define USB_SERIAL_JTAG_INT_ENA_REG 0x10 -#define USB_SERIAL_JTAG_SERIAL_OUT_RECV_PKT_INT_ENA BIT(2) -#define USB_SERIAL_JTAG_SERIAL_IN_EMPTY_INT_ENA BIT(3) -#define USB_SERIAL_JTAG_INT_CLR_REG 0x14 -#define USB_SERIAL_JTAG_IN_EP1_ST_REG 0x2c -#define USB_SERIAL_JTAG_IN_EP1_WR_ADDR GENMASK(8, 2) -#define USB_SERIAL_JTAG_OUT_EP1_ST_REG 0x3c -#define USB_SERIAL_JTAG_OUT_EP1_REC_DATA_CNT GENMASK(22, 16) - -static const struct of_device_id esp32s3_acm_dt_ids[] = { - { - .compatible = "esp,esp32s3-acm", - }, { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, esp32s3_acm_dt_ids); - -static struct uart_port *esp32s3_acm_ports[UART_NR]; - -static void esp32s3_acm_write(struct uart_port *port, unsigned long reg, u32 v) -{ - writel(v, port->membase + reg); -} - -static u32 esp32s3_acm_read(struct uart_port *port, unsigned long reg) -{ - return readl(port->membase + reg); -} - -static u32 esp32s3_acm_tx_fifo_free(struct uart_port *port) -{ - u32 status = esp32s3_acm_read(port, USB_SERIAL_JTAG_EP1_CONF_REG); - - return status & USB_SERIAL_JTAG_SERIAL_IN_EP_DATA_FREE; -} - -static u32 esp32s3_acm_tx_fifo_cnt(struct uart_port *port) -{ - u32 status = esp32s3_acm_read(port, USB_SERIAL_JTAG_IN_EP1_ST_REG); - - return FIELD_GET(USB_SERIAL_JTAG_IN_EP1_WR_ADDR, status); -} - -static u32 esp32s3_acm_rx_fifo_cnt(struct uart_port *port) -{ - u32 status = esp32s3_acm_read(port, USB_SERIAL_JTAG_OUT_EP1_ST_REG); - - return FIELD_GET(USB_SERIAL_JTAG_OUT_EP1_REC_DATA_CNT, status); -} - -/* return TIOCSER_TEMT when transmitter is not busy */ -static unsigned int esp32s3_acm_tx_empty(struct uart_port *port) -{ - return esp32s3_acm_tx_fifo_cnt(port) == 0 ? TIOCSER_TEMT : 0; -} - -static void esp32s3_acm_set_mctrl(struct uart_port *port, unsigned int mctrl) -{ -} - -static unsigned int esp32s3_acm_get_mctrl(struct uart_port *port) -{ - return TIOCM_CAR; -} - -static void esp32s3_acm_stop_tx(struct uart_port *port) -{ - u32 int_ena; - - int_ena = esp32s3_acm_read(port, USB_SERIAL_JTAG_INT_ENA_REG); - int_ena &= ~USB_SERIAL_JTAG_SERIAL_IN_EMPTY_INT_ENA; - esp32s3_acm_write(port, USB_SERIAL_JTAG_INT_ENA_REG, int_ena); -} - -static void esp32s3_acm_rxint(struct uart_port *port) -{ - struct tty_port *tty_port = &port->state->port; - u32 rx_fifo_cnt = esp32s3_acm_rx_fifo_cnt(port); - unsigned long flags; - u32 i; - - if (!rx_fifo_cnt) - return; - - spin_lock_irqsave(&port->lock, flags); - - for (i = 0; i < rx_fifo_cnt; ++i) { - u32 rx = esp32s3_acm_read(port, USB_SERIAL_JTAG_EP1_REG); - - ++port->icount.rx; - tty_insert_flip_char(tty_port, rx, TTY_NORMAL); - } - spin_unlock_irqrestore(&port->lock, flags); - - tty_flip_buffer_push(tty_port); -} - -static void esp32s3_acm_push(struct uart_port *port) -{ - if (esp32s3_acm_tx_fifo_free(port)) - esp32s3_acm_write(port, USB_SERIAL_JTAG_EP1_CONF_REG, - USB_SERIAL_JTAG_WR_DONE); -} - -static void esp32s3_acm_put_char(struct uart_port *port, u8 c) -{ - esp32s3_acm_write(port, USB_SERIAL_JTAG_EP1_REG, c); -} - -static void esp32s3_acm_put_char_sync(struct uart_port *port, u8 c) -{ - unsigned long timeout = jiffies + HZ; - - while (!esp32s3_acm_tx_fifo_free(port)) { - if (time_after(jiffies, timeout)) { - dev_warn(port->dev, "timeout waiting for TX FIFO\n"); - return; - } - cpu_relax(); - } - esp32s3_acm_put_char(port, c); - esp32s3_acm_push(port); -} - -static void esp32s3_acm_transmit_buffer(struct uart_port *port) -{ - u32 tx_fifo_used; - unsigned int pending; - u8 ch; - - if (!esp32s3_acm_tx_fifo_free(port)) - return; - - tx_fifo_used = esp32s3_acm_tx_fifo_cnt(port); - pending = uart_port_tx_limited(port, ch, - ESP32S3_ACM_TX_FIFO_SIZE - tx_fifo_used, - true, esp32s3_acm_put_char(port, ch), - ({})); - if (pending) { - u32 int_ena; - - int_ena = esp32s3_acm_read(port, USB_SERIAL_JTAG_INT_ENA_REG); - int_ena |= USB_SERIAL_JTAG_SERIAL_IN_EMPTY_INT_ENA; - esp32s3_acm_write(port, USB_SERIAL_JTAG_INT_ENA_REG, int_ena); - } - esp32s3_acm_push(port); -} - -static void esp32s3_acm_txint(struct uart_port *port) -{ - esp32s3_acm_transmit_buffer(port); -} - -static irqreturn_t esp32s3_acm_int(int irq, void *dev_id) -{ - struct uart_port *port = dev_id; - u32 status; - - status = esp32s3_acm_read(port, USB_SERIAL_JTAG_INT_ST_REG); - esp32s3_acm_write(port, USB_SERIAL_JTAG_INT_CLR_REG, status); - - if (status & USB_SERIAL_JTAG_SERIAL_OUT_RECV_PKT_INT_ST) - esp32s3_acm_rxint(port); - if (status & USB_SERIAL_JTAG_SERIAL_IN_EMPTY_INT_ST) - esp32s3_acm_txint(port); - - return IRQ_RETVAL(status); -} - -static void esp32s3_acm_start_tx(struct uart_port *port) -{ - esp32s3_acm_transmit_buffer(port); -} - -static void esp32s3_acm_stop_rx(struct uart_port *port) -{ - u32 int_ena; - - int_ena = esp32s3_acm_read(port, USB_SERIAL_JTAG_INT_ENA_REG); - int_ena &= ~USB_SERIAL_JTAG_SERIAL_OUT_RECV_PKT_INT_ENA; - esp32s3_acm_write(port, USB_SERIAL_JTAG_INT_ENA_REG, int_ena); -} - -static int esp32s3_acm_startup(struct uart_port *port) -{ - int ret; - - ret = request_irq(port->irq, esp32s3_acm_int, 0, DRIVER_NAME, port); - if (ret) - return ret; - esp32s3_acm_write(port, USB_SERIAL_JTAG_INT_ENA_REG, - USB_SERIAL_JTAG_SERIAL_OUT_RECV_PKT_INT_ENA); - - return 0; -} - -static void esp32s3_acm_shutdown(struct uart_port *port) -{ - esp32s3_acm_write(port, USB_SERIAL_JTAG_INT_ENA_REG, 0); - free_irq(port->irq, port); -} - -static void esp32s3_acm_set_termios(struct uart_port *port, - struct ktermios *termios, - const struct ktermios *old) -{ -} - -static const char *esp32s3_acm_type(struct uart_port *port) -{ - return "ESP32S3 ACM"; -} - -/* configure/auto-configure the port */ -static void esp32s3_acm_config_port(struct uart_port *port, int flags) -{ - if (flags & UART_CONFIG_TYPE) - port->type = PORT_GENERIC; -} - -#ifdef CONFIG_CONSOLE_POLL -static void esp32s3_acm_poll_put_char(struct uart_port *port, unsigned char c) -{ - esp32s3_acm_put_char_sync(port, c); -} - -static int esp32s3_acm_poll_get_char(struct uart_port *port) -{ - if (esp32s3_acm_rx_fifo_cnt(port)) - return esp32s3_acm_read(port, USB_SERIAL_JTAG_EP1_REG); - else - return NO_POLL_CHAR; -} -#endif - -static const struct uart_ops esp32s3_acm_pops = { - .tx_empty = esp32s3_acm_tx_empty, - .set_mctrl = esp32s3_acm_set_mctrl, - .get_mctrl = esp32s3_acm_get_mctrl, - .stop_tx = esp32s3_acm_stop_tx, - .start_tx = esp32s3_acm_start_tx, - .stop_rx = esp32s3_acm_stop_rx, - .startup = esp32s3_acm_startup, - .shutdown = esp32s3_acm_shutdown, - .set_termios = esp32s3_acm_set_termios, - .type = esp32s3_acm_type, - .config_port = esp32s3_acm_config_port, -#ifdef CONFIG_CONSOLE_POLL - .poll_put_char = esp32s3_acm_poll_put_char, - .poll_get_char = esp32s3_acm_poll_get_char, -#endif -}; - -static void esp32s3_acm_string_write(struct uart_port *port, const char *s, - unsigned int count) -{ - uart_console_write(port, s, count, esp32s3_acm_put_char_sync); -} - -static void -esp32s3_acm_console_write(struct console *co, const char *s, unsigned int count) -{ - struct uart_port *port = esp32s3_acm_ports[co->index]; - unsigned long flags; - bool locked = true; - - if (port->sysrq) - locked = false; - else if (oops_in_progress) - locked = spin_trylock_irqsave(&port->lock, flags); - else - spin_lock_irqsave(&port->lock, flags); - - esp32s3_acm_string_write(port, s, count); - - if (locked) - spin_unlock_irqrestore(&port->lock, flags); -} - -static struct uart_driver esp32s3_acm_reg; -static struct console esp32s3_acm_console = { - .name = DEV_NAME, - .write = esp32s3_acm_console_write, - .device = uart_console_device, - .flags = CON_PRINTBUFFER, - .index = -1, - .data = &esp32s3_acm_reg, -}; - -static void esp32s3_acm_earlycon_write(struct console *con, const char *s, - unsigned int n) -{ - struct earlycon_device *dev = con->data; - - uart_console_write(&dev->port, s, n, esp32s3_acm_put_char_sync); -} - -#ifdef CONFIG_CONSOLE_POLL -static int esp32s3_acm_earlycon_read(struct console *con, char *s, unsigned int n) -{ - struct earlycon_device *dev = con->data; - unsigned int num_read = 0; - - while (num_read < n) { - int c = esp32s3_acm_poll_get_char(&dev->port); - - if (c == NO_POLL_CHAR) - break; - s[num_read++] = c; - } - return num_read; -} -#endif - -static int __init esp32s3_acm_early_console_setup(struct earlycon_device *device, - const char *options) -{ - if (!device->port.membase) - return -ENODEV; - - device->con->write = esp32s3_acm_earlycon_write; -#ifdef CONFIG_CONSOLE_POLL - device->con->read = esp32s3_acm_earlycon_read; -#endif - return 0; -} - -OF_EARLYCON_DECLARE(esp32s3acm, "esp,esp32s3-acm", - esp32s3_acm_early_console_setup); - -static struct uart_driver esp32s3_acm_reg = { - .owner = THIS_MODULE, - .driver_name = DRIVER_NAME, - .dev_name = DEV_NAME, - .nr = ARRAY_SIZE(esp32s3_acm_ports), - .cons = &esp32s3_acm_console, -}; - -static int esp32s3_acm_probe(struct platform_device *pdev) -{ - struct device_node *np = pdev->dev.of_node; - struct uart_port *port; - struct resource *res; - int ret; - - port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL); - if (!port) - return -ENOMEM; - - ret = of_alias_get_id(np, "serial"); - if (ret < 0) { - dev_err(&pdev->dev, "failed to get alias id, errno %d\n", ret); - return ret; - } - if (ret >= UART_NR) { - dev_err(&pdev->dev, "driver limited to %d serial ports\n", - UART_NR); - return -ENOMEM; - } - - port->line = ret; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) - return -ENODEV; - - port->mapbase = res->start; - port->membase = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(port->membase)) - return PTR_ERR(port->membase); - - port->dev = &pdev->dev; - port->type = PORT_GENERIC; - port->iotype = UPIO_MEM; - port->irq = platform_get_irq(pdev, 0); - port->ops = &esp32s3_acm_pops; - port->flags = UPF_BOOT_AUTOCONF; - port->has_sysrq = 1; - port->fifosize = ESP32S3_ACM_TX_FIFO_SIZE; - - esp32s3_acm_ports[port->line] = port; - - platform_set_drvdata(pdev, port); - - return uart_add_one_port(&esp32s3_acm_reg, port); -} - -static void esp32s3_acm_remove(struct platform_device *pdev) -{ - struct uart_port *port = platform_get_drvdata(pdev); - - uart_remove_one_port(&esp32s3_acm_reg, port); -} - - -static struct platform_driver esp32s3_acm_driver = { - .probe = esp32s3_acm_probe, - .remove = esp32s3_acm_remove, - .driver = { - .name = DRIVER_NAME, - .of_match_table = esp32s3_acm_dt_ids, - }, -}; - -static int __init esp32s3_acm_init(void) -{ - int ret; - - ret = uart_register_driver(&esp32s3_acm_reg); - if (ret) - return ret; - - ret = platform_driver_register(&esp32s3_acm_driver); - if (ret) - uart_unregister_driver(&esp32s3_acm_reg); - - return ret; -} - -static void __exit esp32s3_acm_exit(void) -{ - platform_driver_unregister(&esp32s3_acm_driver); - uart_unregister_driver(&esp32s3_acm_reg); -} - -module_init(esp32s3_acm_init); -module_exit(esp32s3_acm_exit); - -MODULE_AUTHOR("Max Filippov "); -MODULE_DESCRIPTION("Espressif ESP32 USB ACM gadget support"); -MODULE_LICENSE("GPL"); diff --git a/drivers/tty/serial/esp32_uart.c b/drivers/tty/serial/esp32_uart.c deleted file mode 100644 index 667c2198a03a..000000000000 --- a/drivers/tty/serial/esp32_uart.c +++ /dev/null @@ -1,779 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DRIVER_NAME "esp32-uart" -#define DEV_NAME "ttyS" -#define UART_NR 3 - -#define ESP32_UART_TX_FIFO_SIZE 127 -#define ESP32_UART_RX_FIFO_SIZE 127 - -#define UART_FIFO_REG 0x00 -#define UART_INT_RAW_REG 0x04 -#define UART_INT_ST_REG 0x08 -#define UART_INT_ENA_REG 0x0c -#define UART_INT_CLR_REG 0x10 -#define UART_RXFIFO_FULL_INT BIT(0) -#define UART_TXFIFO_EMPTY_INT BIT(1) -#define UART_BRK_DET_INT BIT(7) -#define UART_CLKDIV_REG 0x14 -#define ESP32_UART_CLKDIV GENMASK(19, 0) -#define ESP32S3_UART_CLKDIV GENMASK(11, 0) -#define UART_CLKDIV_SHIFT 0 -#define UART_CLKDIV_FRAG GENMASK(23, 20) -#define UART_STATUS_REG 0x1c -#define ESP32_UART_RXFIFO_CNT GENMASK(7, 0) -#define ESP32S3_UART_RXFIFO_CNT GENMASK(9, 0) -#define UART_RXFIFO_CNT_SHIFT 0 -#define UART_DSRN BIT(13) -#define UART_CTSN BIT(14) -#define ESP32_UART_TXFIFO_CNT GENMASK(23, 16) -#define ESP32S3_UART_TXFIFO_CNT GENMASK(25, 16) -#define UART_TXFIFO_CNT_SHIFT 16 -#define UART_CONF0_REG 0x20 -#define UART_PARITY BIT(0) -#define UART_PARITY_EN BIT(1) -#define UART_BIT_NUM GENMASK(3, 2) -#define UART_BIT_NUM_5 0 -#define UART_BIT_NUM_6 1 -#define UART_BIT_NUM_7 2 -#define UART_BIT_NUM_8 3 -#define UART_STOP_BIT_NUM GENMASK(5, 4) -#define UART_STOP_BIT_NUM_1 1 -#define UART_STOP_BIT_NUM_2 3 -#define UART_SW_RTS BIT(6) -#define UART_SW_DTR BIT(7) -#define UART_LOOPBACK BIT(14) -#define UART_TX_FLOW_EN BIT(15) -#define UART_RTS_INV BIT(23) -#define UART_DTR_INV BIT(24) -#define UART_CONF1_REG 0x24 -#define UART_RXFIFO_FULL_THRHD_SHIFT 0 -#define ESP32_UART_TXFIFO_EMPTY_THRHD_SHIFT 8 -#define ESP32S3_UART_TXFIFO_EMPTY_THRHD_SHIFT 10 -#define ESP32_UART_RX_FLOW_EN BIT(23) -#define ESP32S3_UART_RX_FLOW_EN BIT(22) -#define ESP32S3_UART_CLK_CONF_REG 0x78 -#define ESP32S3_UART_SCLK_DIV_B GENMASK(5, 0) -#define ESP32S3_UART_SCLK_DIV_A GENMASK(11, 6) -#define ESP32S3_UART_SCLK_DIV_NUM GENMASK(19, 12) -#define ESP32S3_UART_SCLK_SEL GENMASK(21, 20) -#define APB_CLK 1 -#define RC_FAST_CLK 2 -#define XTAL_CLK 3 -#define ESP32S3_UART_SCLK_EN BIT(22) -#define ESP32S3_UART_RST_CORE BIT(23) -#define ESP32S3_UART_TX_SCLK_EN BIT(24) -#define ESP32S3_UART_RX_SCLK_EN BIT(25) -#define ESP32S3_UART_TX_RST_CORE BIT(26) -#define ESP32S3_UART_RX_RST_CORE BIT(27) - -#define ESP32S3_UART_CLK_CONF_DEFAULT \ - (ESP32S3_UART_RX_SCLK_EN | \ - ESP32S3_UART_TX_SCLK_EN | \ - ESP32S3_UART_SCLK_EN | \ - FIELD_PREP(ESP32S3_UART_SCLK_SEL, XTAL_CLK)) - -struct esp32_port { - struct uart_port port; - struct clk *clk; -}; - -struct esp32_uart_variant { - u32 clkdiv_mask; - u32 rxfifo_cnt_mask; - u32 txfifo_cnt_mask; - u32 txfifo_empty_thrhd_shift; - u32 rx_flow_en; - const char *type; - bool has_clkconf; -}; - -static const struct esp32_uart_variant esp32_variant = { - .clkdiv_mask = ESP32_UART_CLKDIV, - .rxfifo_cnt_mask = ESP32_UART_RXFIFO_CNT, - .txfifo_cnt_mask = ESP32_UART_TXFIFO_CNT, - .txfifo_empty_thrhd_shift = ESP32_UART_TXFIFO_EMPTY_THRHD_SHIFT, - .rx_flow_en = ESP32_UART_RX_FLOW_EN, - .type = "ESP32 UART", -}; - -static const struct esp32_uart_variant esp32s3_variant = { - .clkdiv_mask = ESP32S3_UART_CLKDIV, - .rxfifo_cnt_mask = ESP32S3_UART_RXFIFO_CNT, - .txfifo_cnt_mask = ESP32S3_UART_TXFIFO_CNT, - .txfifo_empty_thrhd_shift = ESP32S3_UART_TXFIFO_EMPTY_THRHD_SHIFT, - .rx_flow_en = ESP32S3_UART_RX_FLOW_EN, - .type = "ESP32S3 UART", - .has_clkconf = true, -}; - -static const struct of_device_id esp32_uart_dt_ids[] = { - { - .compatible = "esp,esp32-uart", - .data = &esp32_variant, - }, { - .compatible = "esp,esp32s3-uart", - .data = &esp32s3_variant, - }, { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, esp32_uart_dt_ids); - -static struct esp32_port *esp32_uart_ports[UART_NR]; - -static const struct esp32_uart_variant *port_variant(struct uart_port *port) -{ - return port->private_data; -} - -static void esp32_uart_write(struct uart_port *port, unsigned long reg, u32 v) -{ - writel(v, port->membase + reg); -} - -static u32 esp32_uart_read(struct uart_port *port, unsigned long reg) -{ - return readl(port->membase + reg); -} - -static u32 esp32_uart_tx_fifo_cnt(struct uart_port *port) -{ - u32 status = esp32_uart_read(port, UART_STATUS_REG); - - return (status & port_variant(port)->txfifo_cnt_mask) >> UART_TXFIFO_CNT_SHIFT; -} - -static u32 esp32_uart_rx_fifo_cnt(struct uart_port *port) -{ - u32 status = esp32_uart_read(port, UART_STATUS_REG); - - return (status & port_variant(port)->rxfifo_cnt_mask) >> UART_RXFIFO_CNT_SHIFT; -} - -/* return TIOCSER_TEMT when transmitter is not busy */ -static unsigned int esp32_uart_tx_empty(struct uart_port *port) -{ - return esp32_uart_tx_fifo_cnt(port) ? 0 : TIOCSER_TEMT; -} - -static void esp32_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) -{ - u32 conf0 = esp32_uart_read(port, UART_CONF0_REG); - - conf0 &= ~(UART_LOOPBACK | - UART_SW_RTS | UART_RTS_INV | - UART_SW_DTR | UART_DTR_INV); - - if (mctrl & TIOCM_RTS) - conf0 |= UART_SW_RTS; - if (mctrl & TIOCM_DTR) - conf0 |= UART_SW_DTR; - if (mctrl & TIOCM_LOOP) - conf0 |= UART_LOOPBACK; - - esp32_uart_write(port, UART_CONF0_REG, conf0); -} - -static unsigned int esp32_uart_get_mctrl(struct uart_port *port) -{ - u32 status = esp32_uart_read(port, UART_STATUS_REG); - unsigned int ret = TIOCM_CAR; - - if (status & UART_DSRN) - ret |= TIOCM_DSR; - if (status & UART_CTSN) - ret |= TIOCM_CTS; - - return ret; -} - -static void esp32_uart_stop_tx(struct uart_port *port) -{ - u32 int_ena; - - int_ena = esp32_uart_read(port, UART_INT_ENA_REG); - int_ena &= ~UART_TXFIFO_EMPTY_INT; - esp32_uart_write(port, UART_INT_ENA_REG, int_ena); -} - -static void esp32_uart_rxint(struct uart_port *port) -{ - struct tty_port *tty_port = &port->state->port; - u32 rx_fifo_cnt = esp32_uart_rx_fifo_cnt(port); - unsigned long flags; - u32 i; - - if (!rx_fifo_cnt) - return; - - spin_lock_irqsave(&port->lock, flags); - - for (i = 0; i < rx_fifo_cnt; ++i) { - u32 rx = esp32_uart_read(port, UART_FIFO_REG); - - if (!rx && - (esp32_uart_read(port, UART_INT_ST_REG) & UART_BRK_DET_INT)) { - esp32_uart_write(port, UART_INT_CLR_REG, UART_BRK_DET_INT); - ++port->icount.brk; - uart_handle_break(port); - } else { - if (uart_handle_sysrq_char(port, (unsigned char)rx)) - continue; - tty_insert_flip_char(tty_port, rx, TTY_NORMAL); - ++port->icount.rx; - } - } - spin_unlock_irqrestore(&port->lock, flags); - - tty_flip_buffer_push(tty_port); -} - -static void esp32_uart_put_char(struct uart_port *port, u8 c) -{ - esp32_uart_write(port, UART_FIFO_REG, c); -} - -static void esp32_uart_put_char_sync(struct uart_port *port, u8 c) -{ - unsigned long timeout = jiffies + HZ; - - while (esp32_uart_tx_fifo_cnt(port) >= ESP32_UART_TX_FIFO_SIZE) { - if (time_after(jiffies, timeout)) { - dev_warn(port->dev, "timeout waiting for TX FIFO\n"); - return; - } - cpu_relax(); - } - esp32_uart_put_char(port, c); -} - -static void esp32_uart_transmit_buffer(struct uart_port *port) -{ - u32 tx_fifo_used = esp32_uart_tx_fifo_cnt(port); - unsigned int pending; - u8 ch; - - if (tx_fifo_used >= ESP32_UART_TX_FIFO_SIZE) - return; - - pending = uart_port_tx_limited(port, ch, - ESP32_UART_TX_FIFO_SIZE - tx_fifo_used, - true, esp32_uart_put_char(port, ch), - ({})); - if (pending) { - u32 int_ena; - - int_ena = esp32_uart_read(port, UART_INT_ENA_REG); - int_ena |= UART_TXFIFO_EMPTY_INT; - esp32_uart_write(port, UART_INT_ENA_REG, int_ena); - } -} - -static void esp32_uart_txint(struct uart_port *port) -{ - esp32_uart_transmit_buffer(port); -} - -static irqreturn_t esp32_uart_int(int irq, void *dev_id) -{ - struct uart_port *port = dev_id; - u32 status; - - status = esp32_uart_read(port, UART_INT_ST_REG); - - if (status & (UART_RXFIFO_FULL_INT | UART_BRK_DET_INT)) - esp32_uart_rxint(port); - if (status & UART_TXFIFO_EMPTY_INT) - esp32_uart_txint(port); - - esp32_uart_write(port, UART_INT_CLR_REG, status); - - return IRQ_RETVAL(status); -} - -static void esp32_uart_start_tx(struct uart_port *port) -{ - esp32_uart_transmit_buffer(port); -} - -static void esp32_uart_stop_rx(struct uart_port *port) -{ - u32 int_ena; - - int_ena = esp32_uart_read(port, UART_INT_ENA_REG); - int_ena &= ~UART_RXFIFO_FULL_INT; - esp32_uart_write(port, UART_INT_ENA_REG, int_ena); -} - -static int esp32_uart_startup(struct uart_port *port) -{ - int ret = 0; - unsigned long flags; - struct esp32_port *sport = container_of(port, struct esp32_port, port); - - ret = clk_prepare_enable(sport->clk); - if (ret) - return ret; - - ret = request_irq(port->irq, esp32_uart_int, 0, DRIVER_NAME, port); - if (ret) { - clk_disable_unprepare(sport->clk); - return ret; - } - - spin_lock_irqsave(&port->lock, flags); - if (port_variant(port)->has_clkconf) - esp32_uart_write(port, ESP32S3_UART_CLK_CONF_REG, - ESP32S3_UART_CLK_CONF_DEFAULT); - esp32_uart_write(port, UART_CONF1_REG, - (1 << UART_RXFIFO_FULL_THRHD_SHIFT) | - (1 << port_variant(port)->txfifo_empty_thrhd_shift)); - esp32_uart_write(port, UART_INT_CLR_REG, UART_RXFIFO_FULL_INT | UART_BRK_DET_INT); - esp32_uart_write(port, UART_INT_ENA_REG, UART_RXFIFO_FULL_INT | UART_BRK_DET_INT); - spin_unlock_irqrestore(&port->lock, flags); - - return ret; -} - -static void esp32_uart_shutdown(struct uart_port *port) -{ - struct esp32_port *sport = container_of(port, struct esp32_port, port); - - esp32_uart_write(port, UART_INT_ENA_REG, 0); - free_irq(port->irq, port); - clk_disable_unprepare(sport->clk); -} - -static bool esp32_uart_set_baud(struct uart_port *port, u32 baud) -{ - u32 sclk = port->uartclk; - u32 div = sclk / baud; - - if (port_variant(port)->has_clkconf) { - u32 sclk_div = div / port_variant(port)->clkdiv_mask; - - if (div > port_variant(port)->clkdiv_mask) { - sclk /= (sclk_div + 1); - div = sclk / baud; - } - esp32_uart_write(port, ESP32S3_UART_CLK_CONF_REG, - FIELD_PREP(ESP32S3_UART_SCLK_DIV_NUM, sclk_div) | - ESP32S3_UART_CLK_CONF_DEFAULT); - } - - if (div <= port_variant(port)->clkdiv_mask) { - u32 frag = (sclk * 16) / baud - div * 16; - - esp32_uart_write(port, UART_CLKDIV_REG, - div | FIELD_PREP(UART_CLKDIV_FRAG, frag)); - return true; - } - - return false; -} - -static void esp32_uart_set_termios(struct uart_port *port, - struct ktermios *termios, - const struct ktermios *old) -{ - unsigned long flags; - u32 conf0, conf1; - u32 baud; - const u32 rx_flow_en = port_variant(port)->rx_flow_en; - u32 max_div = port_variant(port)->clkdiv_mask; - - termios->c_cflag &= ~CMSPAR; - - if (port_variant(port)->has_clkconf) - max_div *= FIELD_MAX(ESP32S3_UART_SCLK_DIV_NUM); - - baud = uart_get_baud_rate(port, termios, old, - port->uartclk / max_div, - port->uartclk / 16); - - spin_lock_irqsave(&port->lock, flags); - - conf0 = esp32_uart_read(port, UART_CONF0_REG); - conf0 &= ~(UART_PARITY_EN | UART_PARITY | UART_BIT_NUM | UART_STOP_BIT_NUM); - - conf1 = esp32_uart_read(port, UART_CONF1_REG); - conf1 &= ~rx_flow_en; - - if (termios->c_cflag & PARENB) { - conf0 |= UART_PARITY_EN; - if (termios->c_cflag & PARODD) - conf0 |= UART_PARITY; - } - - switch (termios->c_cflag & CSIZE) { - case CS5: - conf0 |= FIELD_PREP(UART_BIT_NUM, UART_BIT_NUM_5); - break; - case CS6: - conf0 |= FIELD_PREP(UART_BIT_NUM, UART_BIT_NUM_6); - break; - case CS7: - conf0 |= FIELD_PREP(UART_BIT_NUM, UART_BIT_NUM_7); - break; - case CS8: - conf0 |= FIELD_PREP(UART_BIT_NUM, UART_BIT_NUM_8); - break; - } - - if (termios->c_cflag & CSTOPB) - conf0 |= FIELD_PREP(UART_STOP_BIT_NUM, UART_STOP_BIT_NUM_2); - else - conf0 |= FIELD_PREP(UART_STOP_BIT_NUM, UART_STOP_BIT_NUM_1); - - if (termios->c_cflag & CRTSCTS) - conf1 |= rx_flow_en; - - esp32_uart_write(port, UART_CONF0_REG, conf0); - esp32_uart_write(port, UART_CONF1_REG, conf1); - - if (baud) { - esp32_uart_set_baud(port, baud); - uart_update_timeout(port, termios->c_cflag, baud); - } else { - if (esp32_uart_set_baud(port, 115200)) { - baud = 115200; - tty_termios_encode_baud_rate(termios, baud, baud); - uart_update_timeout(port, termios->c_cflag, baud); - } else { - dev_warn(port->dev, - "unable to set speed to %d baud or the default 115200\n", - baud); - } - } - spin_unlock_irqrestore(&port->lock, flags); -} - -static const char *esp32_uart_type(struct uart_port *port) -{ - return port_variant(port)->type; -} - -/* configure/auto-configure the port */ -static void esp32_uart_config_port(struct uart_port *port, int flags) -{ - if (flags & UART_CONFIG_TYPE) - port->type = PORT_GENERIC; -} - -#ifdef CONFIG_CONSOLE_POLL -static int esp32_uart_poll_init(struct uart_port *port) -{ - struct esp32_port *sport = container_of(port, struct esp32_port, port); - - return clk_prepare_enable(sport->clk); -} - -static void esp32_uart_poll_put_char(struct uart_port *port, unsigned char c) -{ - esp32_uart_put_char_sync(port, c); -} - -static int esp32_uart_poll_get_char(struct uart_port *port) -{ - if (esp32_uart_rx_fifo_cnt(port)) - return esp32_uart_read(port, UART_FIFO_REG); - else - return NO_POLL_CHAR; - -} -#endif - -static const struct uart_ops esp32_uart_pops = { - .tx_empty = esp32_uart_tx_empty, - .set_mctrl = esp32_uart_set_mctrl, - .get_mctrl = esp32_uart_get_mctrl, - .stop_tx = esp32_uart_stop_tx, - .start_tx = esp32_uart_start_tx, - .stop_rx = esp32_uart_stop_rx, - .startup = esp32_uart_startup, - .shutdown = esp32_uart_shutdown, - .set_termios = esp32_uart_set_termios, - .type = esp32_uart_type, - .config_port = esp32_uart_config_port, -#ifdef CONFIG_CONSOLE_POLL - .poll_init = esp32_uart_poll_init, - .poll_put_char = esp32_uart_poll_put_char, - .poll_get_char = esp32_uart_poll_get_char, -#endif -}; - -static void esp32_uart_console_putchar(struct uart_port *port, u8 c) -{ - esp32_uart_put_char_sync(port, c); -} - -static void esp32_uart_string_write(struct uart_port *port, const char *s, - unsigned int count) -{ - uart_console_write(port, s, count, esp32_uart_console_putchar); -} - -static void -esp32_uart_console_write(struct console *co, const char *s, unsigned int count) -{ - struct esp32_port *sport = esp32_uart_ports[co->index]; - struct uart_port *port = &sport->port; - unsigned long flags; - bool locked = true; - - if (port->sysrq) - locked = false; - else if (oops_in_progress) - locked = spin_trylock_irqsave(&port->lock, flags); - else - spin_lock_irqsave(&port->lock, flags); - - esp32_uart_string_write(port, s, count); - - if (locked) - spin_unlock_irqrestore(&port->lock, flags); -} - -static int __init esp32_uart_console_setup(struct console *co, char *options) -{ - struct esp32_port *sport; - int baud = 115200; - int bits = 8; - int parity = 'n'; - int flow = 'n'; - int ret; - - /* - * check whether an invalid uart number has been specified, and - * if so, search for the first available port that does have - * console support. - */ - if (co->index == -1 || co->index >= ARRAY_SIZE(esp32_uart_ports)) - co->index = 0; - - sport = esp32_uart_ports[co->index]; - if (!sport) - return -ENODEV; - - ret = clk_prepare_enable(sport->clk); - if (ret) - return ret; - - if (options) - uart_parse_options(options, &baud, &parity, &bits, &flow); - - return uart_set_options(&sport->port, co, baud, parity, bits, flow); -} - -static int esp32_uart_console_exit(struct console *co) -{ - struct esp32_port *sport = esp32_uart_ports[co->index]; - - clk_disable_unprepare(sport->clk); - return 0; -} - -static struct uart_driver esp32_uart_reg; -static struct console esp32_uart_console = { - .name = DEV_NAME, - .write = esp32_uart_console_write, - .device = uart_console_device, - .setup = esp32_uart_console_setup, - .exit = esp32_uart_console_exit, - .flags = CON_PRINTBUFFER, - .index = -1, - .data = &esp32_uart_reg, -}; - -static void esp32_uart_earlycon_putchar(struct uart_port *port, u8 c) -{ - esp32_uart_put_char_sync(port, c); -} - -static void esp32_uart_earlycon_write(struct console *con, const char *s, - unsigned int n) -{ - struct earlycon_device *dev = con->data; - - uart_console_write(&dev->port, s, n, esp32_uart_earlycon_putchar); -} - -#ifdef CONFIG_CONSOLE_POLL -static int esp32_uart_earlycon_read(struct console *con, char *s, unsigned int n) -{ - struct earlycon_device *dev = con->data; - unsigned int num_read = 0; - - while (num_read < n) { - int c = esp32_uart_poll_get_char(&dev->port); - - if (c == NO_POLL_CHAR) - break; - s[num_read++] = c; - } - return num_read; -} -#endif - -static int __init esp32xx_uart_early_console_setup(struct earlycon_device *device, - const char *options) -{ - if (!device->port.membase) - return -ENODEV; - - device->con->write = esp32_uart_earlycon_write; -#ifdef CONFIG_CONSOLE_POLL - device->con->read = esp32_uart_earlycon_read; -#endif - if (device->port.uartclk != BASE_BAUD * 16) - esp32_uart_set_baud(&device->port, device->baud); - - return 0; -} - -static int __init esp32_uart_early_console_setup(struct earlycon_device *device, - const char *options) -{ - device->port.private_data = (void *)&esp32_variant; - - return esp32xx_uart_early_console_setup(device, options); -} - -OF_EARLYCON_DECLARE(esp32uart, "esp,esp32-uart", - esp32_uart_early_console_setup); - -static int __init esp32s3_uart_early_console_setup(struct earlycon_device *device, - const char *options) -{ - device->port.private_data = (void *)&esp32s3_variant; - - return esp32xx_uart_early_console_setup(device, options); -} - -OF_EARLYCON_DECLARE(esp32s3uart, "esp,esp32s3-uart", - esp32s3_uart_early_console_setup); - -static struct uart_driver esp32_uart_reg = { - .owner = THIS_MODULE, - .driver_name = DRIVER_NAME, - .dev_name = DEV_NAME, - .nr = ARRAY_SIZE(esp32_uart_ports), - .cons = &esp32_uart_console, -}; - -static int esp32_uart_probe(struct platform_device *pdev) -{ - struct device_node *np = pdev->dev.of_node; - struct uart_port *port; - struct esp32_port *sport; - struct resource *res; - int ret; - - sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL); - if (!sport) - return -ENOMEM; - - port = &sport->port; - - ret = of_alias_get_id(np, "serial"); - if (ret < 0) { - dev_err(&pdev->dev, "failed to get alias id, errno %d\n", ret); - return ret; - } - if (ret >= UART_NR) { - dev_err(&pdev->dev, "driver limited to %d serial ports\n", UART_NR); - return -ENOMEM; - } - - port->line = ret; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) - return -ENODEV; - - port->mapbase = res->start; - port->membase = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(port->membase)) - return PTR_ERR(port->membase); - - sport->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(sport->clk)) - return PTR_ERR(sport->clk); - - port->uartclk = clk_get_rate(sport->clk); - port->dev = &pdev->dev; - port->type = PORT_GENERIC; - port->iotype = UPIO_MEM; - port->irq = platform_get_irq(pdev, 0); - port->ops = &esp32_uart_pops; - port->flags = UPF_BOOT_AUTOCONF; - port->has_sysrq = 1; - port->fifosize = ESP32_UART_TX_FIFO_SIZE; - port->private_data = (void *)device_get_match_data(&pdev->dev); - - esp32_uart_ports[port->line] = sport; - - platform_set_drvdata(pdev, port); - - return uart_add_one_port(&esp32_uart_reg, port); -} - -static void esp32_uart_remove(struct platform_device *pdev) -{ - struct uart_port *port = platform_get_drvdata(pdev); - - uart_remove_one_port(&esp32_uart_reg, port); -} - - -static struct platform_driver esp32_uart_driver = { - .probe = esp32_uart_probe, - .remove = esp32_uart_remove, - .driver = { - .name = DRIVER_NAME, - .of_match_table = esp32_uart_dt_ids, - }, -}; - -static int __init esp32_uart_init(void) -{ - int ret; - - ret = uart_register_driver(&esp32_uart_reg); - if (ret) - return ret; - - ret = platform_driver_register(&esp32_uart_driver); - if (ret) - uart_unregister_driver(&esp32_uart_reg); - - return ret; -} - -static void __exit esp32_uart_exit(void) -{ - platform_driver_unregister(&esp32_uart_driver); - uart_unregister_driver(&esp32_uart_reg); -} - -module_init(esp32_uart_init); -module_exit(esp32_uart_exit); - -MODULE_AUTHOR("Max Filippov "); -MODULE_DESCRIPTION("Espressif ESP32 UART support"); -MODULE_LICENSE("GPL"); From 24728b93fafe0949b5353e1a7b3a94175fe26d6e Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 10 Mar 2026 22:23:47 -0700 Subject: [PATCH 12/35] serdev: serdev.h: clean up kernel-doc comments Correct kernel-doc comment format and add a missing to avoid kernel-doc warnings: Warning: include/linux/serdev.h:49 struct member 'write_comp' not described in 'serdev_device' Warning: include/linux/serdev.h:49 struct member 'write_lock' not described in 'serdev_device' Warning: include/linux/serdev.h:68 struct member 'shutdown' not described in 'serdev_device_driver' Warning: include/linux/serdev.h:134 function parameter 'serdev' not described in 'serdev_device_put' Warning: include/linux/serdev.h:162 function parameter 'ctrl' not described in 'serdev_controller_put' Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260311052347.305612-1-rdunlap@infradead.org Signed-off-by: Greg Kroah-Hartman --- include/linux/serdev.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/include/linux/serdev.h b/include/linux/serdev.h index 5654c58eb73c..090c93c08045 100644 --- a/include/linux/serdev.h +++ b/include/linux/serdev.h @@ -37,8 +37,8 @@ struct serdev_device_ops { * @nr: Device number on serdev bus. * @ctrl: serdev controller managing this device. * @ops: Device operations. - * @write_comp Completion used by serdev_device_write() internally - * @write_lock Lock to serialize access when writing data + * @write_comp: Completion used by serdev_device_write() internally + * @write_lock: Lock to serialize access when writing data */ struct serdev_device { struct device dev; @@ -60,6 +60,7 @@ static inline struct serdev_device *to_serdev_device(struct device *d) * structure. * @probe: binds this driver to a serdev device. * @remove: unbinds this driver from the serdev device. + * @shutdown: shut down this serdev device. */ struct serdev_device_driver { struct device_driver driver; @@ -129,7 +130,7 @@ static inline void serdev_device_set_drvdata(struct serdev_device *serdev, void /** * serdev_device_put() - decrement serdev device refcount - * @serdev serdev device. + * @serdev: serdev device. */ static inline void serdev_device_put(struct serdev_device *serdev) { @@ -157,7 +158,7 @@ static inline void serdev_controller_set_drvdata(struct serdev_controller *ctrl, /** * serdev_controller_put() - decrement controller refcount - * @ctrl serdev controller. + * @ctrl: serdev controller. */ static inline void serdev_controller_put(struct serdev_controller *ctrl) { From 4af70f151671da6acd7a1d7bae1469c576673d2d Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Mon, 2 Feb 2026 23:52:46 -0500 Subject: [PATCH 13/35] vt: add modifier support to cursor keys Generate xterm-style CSI sequences with modifier parameters for arrow keys when Shift, Alt, or Ctrl are held. For example, Shift+Up produces ESC [ 1 ; 2 A instead of plain ESC [ A. The modifier encoding follows the standard xterm convention: mod = 1 + (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0) When no modifiers are pressed, the original behavior is preserved. Explicit keymap bindings for modified cursor keys (e.g., "shift keycode 103 = Find") take precedence over this automatic modifier encoding. Signed-off-by: Nicolas Pitre Link: https://patch.msgid.link/20260203045457.1049793-2-nico@fluxnic.net Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/keyboard.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c index 13bc048f45e8..cb907a3b9d3d 100644 --- a/drivers/tty/vt/keyboard.c +++ b/drivers/tty/vt/keyboard.c @@ -765,14 +765,39 @@ static void k_fn(struct vc_data *vc, unsigned char value, char up_flag) pr_err("k_fn called with value=%d\n", value); } +/* + * Compute xterm-style modifier parameter for CSI sequences. + * Returns 1 + (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0) + */ +static int csi_modifier_param(void) +{ + int mod = 1; + + if (shift_state & (BIT(KG_SHIFT) | BIT(KG_SHIFTL) | BIT(KG_SHIFTR))) + mod += 1; + if (shift_state & (BIT(KG_ALT) | BIT(KG_ALTGR))) + mod += 2; + if (shift_state & (BIT(KG_CTRL) | BIT(KG_CTRLL) | BIT(KG_CTRLR))) + mod += 4; + return mod; +} + static void k_cur(struct vc_data *vc, unsigned char value, char up_flag) { static const char cur_chars[] = "BDCA"; + int mod; if (up_flag) return; - applkey(vc, cur_chars[value], vc_kbd_mode(kbd, VC_CKMODE)); + mod = csi_modifier_param(); + if (mod > 1) { + char buf[] = { 0x1b, '[', '1', ';', '0' + mod, cur_chars[value], 0x00 }; + + puts_queue(vc, buf); + } else { + applkey(vc, cur_chars[value], vc_kbd_mode(kbd, VC_CKMODE)); + } } static void k_pad(struct vc_data *vc, unsigned char value, char up_flag) From 5cba06c71c713a5beb4aafab7973287d8a248ddb Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Mon, 2 Feb 2026 23:52:47 -0500 Subject: [PATCH 14/35] vt: add KT_CSI keysym type for modifier-aware CSI sequences Add a new keysym type KT_CSI that generates CSI tilde sequences with automatic modifier encoding. The keysym value encodes the CSI parameter number, producing sequences like ESC [ ~ or ESC [ ; ~ when Shift, Alt, or Ctrl modifiers are held. This allows navigation keys (Home, End, Insert, Delete, PgUp, PgDn) and function keys to generate modifier-aware escape sequences without consuming string table entries for each modifier combination. Define key symbols for navigation keys (K_CSI_HOME, K_CSI_END, etc.) and function keys (K_CSI_F1 through K_CSI_F20) using standard xterm CSI parameter values. The modifier encoding follows the xterm convention: mod = 1 + (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0) Allowed CSI parameter values range from 0 to 99. Note: The Linux console historically uses a non-standard double-bracket format for F1-F5 (ESC [ [ A through ESC [ [ E) rather than the xterm tilde format (ESC [ 11 ~ through ESC [ 15 ~). The K_CSI_F1 through K_CSI_F5 definitions use the xterm format. Converting F1-F5 to KT_CSI would require updating the "linux" terminfo entry to match. Navigation keys and F6-F20 already use the tilde format and are fully compatible. Signed-off-by: Nicolas Pitre Link: https://patch.msgid.link/20260203045457.1049793-3-nico@fluxnic.net Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/keyboard.c | 38 ++++++++++++++++++++++++++++++----- include/uapi/linux/keyboard.h | 29 ++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c index cb907a3b9d3d..44fd67eb723a 100644 --- a/drivers/tty/vt/keyboard.c +++ b/drivers/tty/vt/keyboard.c @@ -74,7 +74,7 @@ static inline int kbd_defleds(void) k_self, k_fn, k_spec, k_pad,\ k_dead, k_cons, k_cur, k_shift,\ k_meta, k_ascii, k_lock, k_lowercase,\ - k_slock, k_dead2, k_brl, k_ignore + k_slock, k_dead2, k_brl, k_csi typedef void (k_handler_fn)(struct vc_data *vc, unsigned char value, char up_flag); @@ -127,6 +127,7 @@ static const unsigned char max_vals[] = { [ KT_SLOCK ] = NR_LOCK - 1, [ KT_DEAD2 ] = 255, [ KT_BRL ] = NR_BRL - 1, + [ KT_CSI ] = 99, }; static const int NR_TYPES = ARRAY_SIZE(max_vals); @@ -644,10 +645,6 @@ static void fn_null(struct vc_data *vc) /* * Special key handlers */ -static void k_ignore(struct vc_data *vc, unsigned char value, char up_flag) -{ -} - static void k_spec(struct vc_data *vc, unsigned char value, char up_flag) { if (up_flag) @@ -1029,6 +1026,37 @@ static void k_brl(struct vc_data *vc, unsigned char value, char up_flag) } } +/* + * Handle KT_CSI keysym type: generate CSI tilde sequences with modifier + * support. The value encodes the CSI parameter number, producing sequences + * like ESC [ ~ or ESC [ ; ~ when modifiers are held. + */ +static void k_csi(struct vc_data *vc, unsigned char value, char up_flag) +{ + char buf[10]; + int i = 0; + int mod; + + if (up_flag) + return; + + mod = csi_modifier_param(); + + buf[i++] = 0x1b; + buf[i++] = '['; + if (value >= 10) + buf[i++] = '0' + value / 10; + buf[i++] = '0' + value % 10; + if (mod > 1) { + buf[i++] = ';'; + buf[i++] = '0' + mod; + } + buf[i++] = '~'; + buf[i] = 0x00; + + puts_queue(vc, buf); +} + #if IS_ENABLED(CONFIG_INPUT_LEDS) && IS_ENABLED(CONFIG_LEDS_TRIGGERS) struct kbd_led_trigger { diff --git a/include/uapi/linux/keyboard.h b/include/uapi/linux/keyboard.h index 36d230cedf12..48ecb0cefb45 100644 --- a/include/uapi/linux/keyboard.h +++ b/include/uapi/linux/keyboard.h @@ -41,6 +41,7 @@ #define KT_SLOCK 12 #define KT_DEAD2 13 #define KT_BRL 14 +#define KT_CSI 15 /* CSI sequences with modifier support */ #define K(t,v) (((t)<<8)|(v)) #define KTYP(x) ((x) >> 8) @@ -461,5 +462,33 @@ #define NR_BRL 11 +/* KT_CSI keys: value is the CSI parameter number for ESC [ ~ */ +#define K_CSI_HOME K(KT_CSI, 1) /* ESC [ 1 ~ */ +#define K_CSI_INSERT K(KT_CSI, 2) /* ESC [ 2 ~ */ +#define K_CSI_DELETE K(KT_CSI, 3) /* ESC [ 3 ~ */ +#define K_CSI_END K(KT_CSI, 4) /* ESC [ 4 ~ */ +#define K_CSI_PGUP K(KT_CSI, 5) /* ESC [ 5 ~ */ +#define K_CSI_PGDN K(KT_CSI, 6) /* ESC [ 6 ~ */ +#define K_CSI_F1 K(KT_CSI, 11) /* ESC [ 11 ~ */ +#define K_CSI_F2 K(KT_CSI, 12) /* ESC [ 12 ~ */ +#define K_CSI_F3 K(KT_CSI, 13) /* ESC [ 13 ~ */ +#define K_CSI_F4 K(KT_CSI, 14) /* ESC [ 14 ~ */ +#define K_CSI_F5 K(KT_CSI, 15) /* ESC [ 15 ~ */ +#define K_CSI_F6 K(KT_CSI, 17) /* ESC [ 17 ~ */ +#define K_CSI_F7 K(KT_CSI, 18) /* ESC [ 18 ~ */ +#define K_CSI_F8 K(KT_CSI, 19) /* ESC [ 19 ~ */ +#define K_CSI_F9 K(KT_CSI, 20) /* ESC [ 20 ~ */ +#define K_CSI_F10 K(KT_CSI, 21) /* ESC [ 21 ~ */ +#define K_CSI_F11 K(KT_CSI, 23) /* ESC [ 23 ~ */ +#define K_CSI_F12 K(KT_CSI, 24) /* ESC [ 24 ~ */ +#define K_CSI_F13 K(KT_CSI, 25) /* ESC [ 25 ~ */ +#define K_CSI_F14 K(KT_CSI, 26) /* ESC [ 26 ~ */ +#define K_CSI_F15 K(KT_CSI, 28) /* ESC [ 28 ~ */ +#define K_CSI_F16 K(KT_CSI, 29) /* ESC [ 29 ~ */ +#define K_CSI_F17 K(KT_CSI, 31) /* ESC [ 31 ~ */ +#define K_CSI_F18 K(KT_CSI, 32) /* ESC [ 32 ~ */ +#define K_CSI_F19 K(KT_CSI, 33) /* ESC [ 33 ~ */ +#define K_CSI_F20 K(KT_CSI, 34) /* ESC [ 34 ~ */ + #define MAX_DIACR 256 #endif /* _UAPI__LINUX_KEYBOARD_H */ From c1d2deb6492fbd900392f4ebd47572ca4903d0fe Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Mon, 2 Feb 2026 23:52:48 -0500 Subject: [PATCH 15/35] vt: add fallback to plain map for modifier-aware key types When a key is pressed with modifiers (Shift, Ctrl, Alt, etc.) and the modifier-specific keymap has no binding (K_HOLE) or doesn't exist, fall back to the plain keymap if the plain entry is a modifier-aware type (KT_CUR or KT_CSI). This allows arrow keys and CSI navigation keys to automatically handle all modifier combinations with just a single plain map entry. The key handlers (k_cur and k_csi) read the modifier state at runtime and encode it into the output sequence. For example, with just: keycode 103 = Up keycode 104 = Csi_Home All these combinations now work automatically: Up -> ESC [ A Shift+Up -> ESC [ 1 ; 2 A Ctrl+Up -> ESC [ 1 ; 5 A Home -> ESC [ 1 ~ Shift+Home -> ESC [ 1 ; 2 ~ Ctrl+Home -> ESC [ 1 ; 5 ~ Previously, each modifier combination required an explicit keymap entry, which was tedious and consumed keymap slots. Explicit modifier bindings still take precedence - the fallback only triggers when the modifier-specific entry is empty. Signed-off-by: Nicolas Pitre Link: https://patch.msgid.link/20260203045457.1049793-4-nico@fluxnic.net Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/keyboard.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c index 44fd67eb723a..dfdea0842149 100644 --- a/drivers/tty/vt/keyboard.c +++ b/drivers/tty/vt/keyboard.c @@ -1498,6 +1498,21 @@ static void kbd_keycode(unsigned int keycode, int down, bool hw_raw) param.ledstate = kbd->ledflagstate; key_map = key_maps[shift_final]; + /* + * Fall back to the plain map if modifiers are active, the modifier- + * specific map is missing or has no entry, and the plain map has a + * modifier-aware key type (KT_CUR or KT_CSI). These handlers encode + * the modifier state into the emitted escape sequence. + */ + if (shift_final && keycode < NR_KEYS && + (!key_map || key_map[keycode] == K_HOLE) && key_maps[0]) { + unsigned short plain = key_maps[0][keycode]; + unsigned char type = KTYP(plain); + + if (type >= 0xf0 && (type - 0xf0 == KT_CUR || type - 0xf0 == KT_CSI)) + key_map = key_maps[0]; + } + rc = atomic_notifier_call_chain(&keyboard_notifier_list, KBD_KEYCODE, ¶m); if (rc == NOTIFY_STOP || !key_map) { From 2c8c3487b25b2e599528299224a54941e0b835ed Mon Sep 17 00:00:00 2001 From: Zhaoyang Yu <2426767509@qq.com> Date: Sun, 1 Mar 2026 16:22:56 +0000 Subject: [PATCH 16/35] serial: auart: check clk_enable() return in console write Add a check for clk_enable() in auart_console_write(). If clk_enable() fails, return immediately to avoid accessing hardware registers while the clock is not enabled. Signed-off-by: Zhaoyang Yu <2426767509@qq.com> Reviewed-by: Frank Li Link: https://patch.msgid.link/tencent_AB29FADF1FAD67D818283B6BB4FDF66F2F08@qq.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/mxs-auart.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c index cc65c9fb6446..693b491f1e75 100644 --- a/drivers/tty/serial/mxs-auart.c +++ b/drivers/tty/serial/mxs-auart.c @@ -1318,7 +1318,8 @@ auart_console_write(struct console *co, const char *str, unsigned int count) s = auart_port[co->index]; port = &s->port; - clk_enable(s->clk); + if (clk_enable(s->clk)) + return; /* First save the CR then disable the interrupts */ old_ctrl2 = mxs_read(s, REG_CTRL2); From 0e5cb010e37d24a03d3e3a3565900cba4df8a7f0 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:09 +0100 Subject: [PATCH 17/35] dt-bindings: serial: atmel,at91-usart: add microchip,lan9691-usart Document Microchip LAN969x USART compatible. Signed-off-by: Robert Marko Acked-by: Conor Dooley Reviewed-by: Claudiu Beznea Link: https://patch.msgid.link/20260302112153.464422-2-robert.marko@sartura.hr Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/serial/atmel,at91-usart.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/serial/atmel,at91-usart.yaml b/Documentation/devicetree/bindings/serial/atmel,at91-usart.yaml index 087a8926f8b4..375cd50bc5cc 100644 --- a/Documentation/devicetree/bindings/serial/atmel,at91-usart.yaml +++ b/Documentation/devicetree/bindings/serial/atmel,at91-usart.yaml @@ -24,6 +24,7 @@ properties: - const: atmel,at91sam9260-usart - items: - enum: + - microchip,lan9691-usart - microchip,sam9x60-usart - microchip,sam9x7-usart - microchip,sama7d65-usart From 579ab531225e25f50848109a0e7238dc86803088 Mon Sep 17 00:00:00 2001 From: Xianwei Zhao Date: Tue, 3 Mar 2026 10:05:24 +0000 Subject: [PATCH 18/35] dt-bindings: serial: amlogic,meson-uart: Add compatible string for A9 Amlogic A9 SoCs uses the same UART controller as S4 SoCs. There is no need for an extra compatible line in the driver, but add A9 compatible line for documentation. Reviewed-by: Martin Blumenstingl Acked-by: Krzysztof Kozlowski Signed-off-by: Xianwei Zhao Link: https://patch.msgid.link/20260303-serial-binding-v1-1-c3df2a8f6fa3@amlogic.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/serial/amlogic,meson-uart.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/serial/amlogic,meson-uart.yaml b/Documentation/devicetree/bindings/serial/amlogic,meson-uart.yaml index d8ad1bb6172d..a2702319685d 100644 --- a/Documentation/devicetree/bindings/serial/amlogic,meson-uart.yaml +++ b/Documentation/devicetree/bindings/serial/amlogic,meson-uart.yaml @@ -56,6 +56,7 @@ properties: items: - enum: - amlogic,a4-uart + - amlogic,a9-uart - amlogic,s6-uart - amlogic,s7-uart - amlogic,s7d-uart From dcb822503bfc77fe93e8ca15c82077ee590dd7b9 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Tue, 3 Mar 2026 12:14:38 +0100 Subject: [PATCH 19/35] serial: tegra: remove Kconfig dependency on APB DMA controller This driver runs also on SoCs without a Tegra20 APB DMA controller (e.g. Tegra234). Remove the Kconfig dependency on TEGRA20_APB_DMA, and remove reference to the APB DMA controller from the Kconfig help text. Signed-off-by: Francesco Lavra Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260303111438.2691799-1-flavra@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/Kconfig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 686e7fb073b8..b8571431ba9e 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -255,14 +255,13 @@ config SERIAL_SAMSUNG_CONSOLE config SERIAL_TEGRA tristate "NVIDIA Tegra20/30 SoC serial controller" - depends on (ARCH_TEGRA && TEGRA20_APB_DMA) || COMPILE_TEST + depends on ARCH_TEGRA || COMPILE_TEST select SERIAL_CORE help Support for the on-chip UARTs on the NVIDIA Tegra series SOCs providing /dev/ttyTHS0, 1, 2, 3 and 4 (note, some machines may not provide all of these ports, depending on how the serial port - are enabled). This driver uses the APB DMA to achieve higher baudrate - and better performance. + are enabled). config SERIAL_TEGRA_TCU tristate "NVIDIA Tegra Combined UART" From 37b4cab642f285176ed392e2f5a467a531424f90 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Sun, 22 Feb 2026 18:29:08 -0500 Subject: [PATCH 20/35] serial: pic32_uart: allow driver to be compiled on all architectures with COMPILE_TEST This driver currently only supports builds against a PIC32 target, or with COMPILE_TEST on MIPS. Now that commit 24cad1a22848 ("serial: pic32_uart: update include to use pic32.h from platform_data") is merged, it's possible to compile this driver on other architectures. To avoid future breakage of this driver in the future, let's update the Kconfig so that it can be built with COMPILE_TEST enabled on all architectures. Signed-off-by: Brian Masney Link: https://patch.msgid.link/20260222-serial-pic32-v1-1-8fdbc0d0d334@redhat.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index b8571431ba9e..9aa61c93d7bc 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -802,7 +802,7 @@ config SERIAL_CPM_CONSOLE config SERIAL_PIC32 tristate "Microchip PIC32 serial support" - depends on MACH_PIC32 || (MIPS && COMPILE_TEST) + depends on MACH_PIC32 || COMPILE_TEST select SERIAL_CORE help If you have a PIC32, this driver supports the serial ports. From 072ce4812b2f8c178d035c5837e17420ec4a3167 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Wed, 25 Feb 2026 09:17:23 +0100 Subject: [PATCH 21/35] tty: serial: 8250: Add SystemBase Multi I/O cards Add support for the SystemBase Multi I/O serial cards, which are "compatible" with a standard 16550A controllers, except that they need to have their interrupts enabled in a proprietary way. Tested with a Delock "Serial PCI Express x1 Card 8x Serial RS-232". Signed-off-by: Michael Walle Link: https://patch.msgid.link/20260225081739.946723-1-mwalle@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_pci.c | 51 ++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c index aa1ab4da9ff1..e2bea501d3cf 100644 --- a/drivers/tty/serial/8250/8250_pci.c +++ b/drivers/tty/serial/8250/8250_pci.c @@ -100,6 +100,8 @@ #define PCI_DEVICE_ID_ADDIDATA_CPCI7420_NG 0x7025 #define PCI_DEVICE_ID_ADDIDATA_CPCI7300_NG 0x7026 +#define PCI_VENDOR_ID_SYSTEMBASE 0x14a1 + /* Unknown vendors/cards - this should not be in linux/pci_ids.h */ #define PCI_SUBDEVICE_ID_UNKNOWN_0x1584 0x1584 #define PCI_SUBDEVICE_ID_UNKNOWN_0x1588 0x1588 @@ -2128,6 +2130,35 @@ pci_moxa_setup(struct serial_private *priv, return setup_port(priv, port, bar, offset, 0); } +#define SB_OPTR_IMR0 0x0c /* Interrupt mask register, p0 to p7 */ +static int pci_systembase_init(struct pci_dev *dev) +{ + resource_size_t iobase; + + if (!IS_ENABLED(CONFIG_HAS_IOPORT)) + return serial_8250_warn_need_ioport(dev); + + iobase = pci_resource_start(dev, 1); + + /* This will support up to 8 ports */ + outb(0xff, iobase + SB_OPTR_IMR0); + + return 0; +} + +static void pci_systembase_exit(struct pci_dev *dev) +{ + resource_size_t iobase; + + if (!IS_ENABLED(CONFIG_HAS_IOPORT)) { + serial_8250_warn_need_ioport(dev); + return; + } + + iobase = pci_resource_start(dev, 0); + outb(0x00, iobase + SB_OPTR_IMR0); +} + /* * Master list of serial port init/setup/exit quirks. * This does not describe the general nature of the port. @@ -2476,6 +2507,16 @@ static struct pci_serial_quirk pci_serial_quirks[] = { .init = pci_siig_init, .setup = pci_siig_setup, }, + /* Systembase */ + { + .vendor = PCI_VENDOR_ID_SYSTEMBASE, + .device = 0x0008, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_systembase_init, + .setup = pci_default_setup, + .exit = pci_systembase_exit, + }, /* * Titan cards */ @@ -3041,6 +3082,7 @@ enum pci_board_num_t { pbn_b0_1_921600, pbn_b0_2_921600, pbn_b0_4_921600, + pbn_b0_8_921600, pbn_b0_2_1130000, @@ -3241,6 +3283,12 @@ static struct pciserial_board pci_boards[] = { .base_baud = 921600, .uart_offset = 8, }, + [pbn_b0_8_921600] = { + .flags = FL_BASE0, + .num_ports = 8, + .base_baud = 921600, + .uart_offset = 8, + }, [pbn_b0_2_1130000] = { .flags = FL_BASE0, @@ -6152,6 +6200,9 @@ static const struct pci_device_id serial_pci_tbl[] = { PCI_ANY_ID, PCI_ANY_ID, 0, 0, pbn_b0_1_115200 }, + /* Systembase Multi I/O cards */ + { PCI_VDEVICE(SYSTEMBASE, 0x0008), pbn_b0_8_921600 }, + /* Fintek PCI serial cards */ { PCI_DEVICE(0x1c29, 0x1104), .driver_data = pbn_fintek_4 }, { PCI_DEVICE(0x1c29, 0x1108), .driver_data = pbn_fintek_8 }, From 74e0c9f0528bcd597cb1299a027d7be27d1c27d9 Mon Sep 17 00:00:00 2001 From: Robin Gong Date: Thu, 12 Mar 2026 17:45:26 +0800 Subject: [PATCH 22/35] tty: serial: imx: keep dma request disabled before dma transfer setup Since sdma hardware configure postpone to transfer phase, have to disable dma request before dma transfer setup because there is a hardware limitation on sdma event enable(ENBLn) as below. Refer SDMA 2.6.28 Channel Enable RAM (SDMAARMx_CHNENBLn) section: "It is thus essential for the Arm platform to program them before any DMA request is triggered to the SDMA, otherwise an unpredictable combination of channels may be started." Signed-off-by: Robin Gong Signed-off-by: Sherry Sun Link: https://patch.msgid.link/20260312094526.297348-1-sherry.sun@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index c488e5d372ff..251a50c8aa38 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -1442,9 +1442,9 @@ static void imx_uart_enable_dma(struct imx_port *sport) imx_uart_setup_ufcr(sport, TXTL_DMA, RXTL_DMA); - /* set UCR1 */ + /* set UCR1 except TXDMAEN which would be enabled in imx_uart_dma_tx */ ucr1 = imx_uart_readl(sport, UCR1); - ucr1 |= UCR1_RXDMAEN | UCR1_TXDMAEN | UCR1_ATDMAEN; + ucr1 |= UCR1_RXDMAEN | UCR1_ATDMAEN; imx_uart_writel(sport, ucr1, UCR1); sport->dma_is_enabled = 1; @@ -1567,8 +1567,9 @@ static int imx_uart_startup(struct uart_port *port) imx_uart_enable_ms(&sport->port); if (dma_is_inited) { - imx_uart_enable_dma(sport); + /* Note: enable dma request after transfer start! */ imx_uart_start_rx_dma(sport); + imx_uart_enable_dma(sport); } else { ucr1 = imx_uart_readl(sport, UCR1); ucr1 |= UCR1_RRDYEN; From 0b1837c04d2335ec50b9a55b0282dcde7bc12439 Mon Sep 17 00:00:00 2001 From: Anup Kulkarni Date: Tue, 10 Mar 2026 16:11:55 +0530 Subject: [PATCH 23/35] serial: qcom-geni: Fix RTS behavior with flow control When userspace enables flow control (CRTSCTS), the driver deasserts RTS even when the receive buffer has space. This prevents the peer device from transmitting, causing communication to stall. The root cause is that the driver unconditionally uses manual RTS control regardless of flow control mode. When CRTSCTS is set, the hardware should automatically manage RTS based on buffer status, but the driver overrides this by setting manual control. Fix this by introducing port->manual_flow flag. In set_termios(), disable manual flow when CRTSCTS is set. In set_mctrl(), only assert SE_UART_MANUAL_RFR when manual_flow is active. Verified by enabling and disabling hardware flow control with stty. Signed-off-by: Anup Kulkarni Link: https://patch.msgid.link/20260310104155.339010-1-anup.kulkarni@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index e6b0a55f0cfb..9854bb2406e3 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -146,6 +146,7 @@ struct qcom_geni_serial_port { int wakeup_irq; bool rx_tx_swap; bool cts_rts_swap; + bool manual_flow; struct qcom_geni_private_data private_data; const struct qcom_geni_device_data *dev_data; @@ -250,7 +251,7 @@ static void qcom_geni_serial_set_mctrl(struct uart_port *uport, if (mctrl & TIOCM_LOOP) port->loopback = RX_TX_CTS_RTS_SORTED; - if (!(mctrl & TIOCM_RTS) && !uport->suspended) + if (port->manual_flow && !(mctrl & TIOCM_RTS) && !uport->suspended) uart_manual_rfr = UART_MANUAL_RFR_EN | UART_RFR_NOT_READY; writel(uart_manual_rfr, uport->membase + SE_UART_MANUAL_RFR); } @@ -1401,11 +1402,21 @@ static void qcom_geni_serial_set_termios(struct uart_port *uport, else stop_bit_len = TX_STOP_BIT_LEN_1; - /* flow control, clear the CTS_MASK bit if using flow control. */ - if (termios->c_cflag & CRTSCTS) + /* Configure flow control based on CRTSCTS flag. + * When CRTSCTS is set, use HW/auto flow control mode, where HW + * controls the RTS/CTS pin based FIFO state. + * When CRTSCTS is clear, the CTS pin value is ignored for TX + * path and RTS pin can be set/cleared using registers, for RX + * path. + */ + + if (termios->c_cflag & CRTSCTS) { tx_trans_cfg &= ~UART_CTS_MASK; - else + port->manual_flow = false; + } else { tx_trans_cfg |= UART_CTS_MASK; + port->manual_flow = true; + } if (baud) { uart_update_timeout(uport, termios->c_cflag, baud); From fa4268cfe899b684d628e9419705a236c2ae589a Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Mon, 2 Mar 2026 18:02:22 -0700 Subject: [PATCH 24/35] vt: support ITU-T T.416 color subparameters The colon ("bit combination 03/10") is a valid character in parameter substrings. ECMA-48 says: Each parameter sub-string consists of one or more bit combinations from 03/00 to 03/10; the bit combinations from 03/00 to 03/09 represent the digits ZERO to NINE; bit combination 03/10 may be used as a separator in a parameter sub-string, for example, to separate the fractional part of a decimal number from the integer part of that number. To my knowledge, the only codes where 03/10 is actually used as a separator are the CSI-m SGR sequences. The colon separated format is superior as an embedded string for software that doesn't wish to link ncurses terminal database, because terminals that do not support the requested SGR sequence can safely skip the sub-parameters rather than misinterpret them as another sequence. Hence, some software have started using this "modern" format [1]. We should support the colon separated format as well. [1] https://github.com/systemd/systemd/commit/6eabe9f2ff48c1b6924724d5afe64e7b661ccdbf Signed-off-by: Ronan Pigott Link: https://patch.msgid.link/20260303010701.631022-1-ronan@rjp.ie Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/vt.c | 48 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index c1f152d8b03b..16010bbc76d7 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -1644,9 +1644,7 @@ static void rgb_background(struct vc_data *vc, const struct rgb *c) /* * ITU T.416 Higher colour modes. They break the usual properties of SGR codes - * and thus need to be detected and ignored by hand. That standard also - * wants : rather than ; as separators but sequences containing : are currently - * completely ignored by the parser. + * and thus need to be detected and ignored by hand. * * Subcommands 3 (CMY) and 4 (CMYK) are so insane there's no point in * supporting them. @@ -1703,6 +1701,7 @@ enum { CSI_m_BG_COLOR_END = 47, CSI_m_BG_COLOR = 48, CSI_m_DEFAULT_BG_COLOR = 49, + CSI_m_UNDERLINE_COLOR = 58, CSI_m_BRIGHT_FG_COLOR_BEG = 90, CSI_m_BRIGHT_FG_COLOR_END = 97, CSI_m_BRIGHT_FG_COLOR_OFF = CSI_m_BRIGHT_FG_COLOR_BEG - CSI_m_FG_COLOR_BEG, @@ -2160,6 +2159,7 @@ static void restore_cur(struct vc_data *vc) * @ESesc: ESC parsed * @ESsquare: CSI parsed -- modifiers/parameters/ctrl chars expected * @ESgetpars: CSI parsed -- parameters/ctrl chars expected + * @ESgetsubpars: CSI m parsed -- subparameters expected * @ESfunckey: CSI [ parsed * @EShash: ESC # parsed * @ESsetG0: ESC ( parsed @@ -2180,6 +2180,7 @@ enum vc_ctl_state { ESesc, ESsquare, ESgetpars, + ESgetsubpars, ESfunckey, EShash, ESsetG0, @@ -2699,6 +2700,47 @@ static void do_con_trol(struct tty_struct *tty, struct vc_data *vc, u8 c) fallthrough; case ESgetpars: /* ESC [ aka CSI, parameters expected */ switch (c) { + case ':': /* ITU-T T.416 color subparameters */ + if (vc->vc_par[vc->vc_npar] == CSI_m_FG_COLOR || + vc->vc_par[vc->vc_npar] == CSI_m_BG_COLOR || + vc->vc_par[vc->vc_npar] == CSI_m_UNDERLINE_COLOR) + vc->vc_state = ESgetsubpars; + else + break; + fallthrough; + case ';': + if (vc->vc_npar < NPAR - 1) { + vc->vc_npar++; + return; + } + break; + case '0' ... '9': + vc->vc_par[vc->vc_npar] *= 10; + vc->vc_par[vc->vc_npar] += c - '0'; + return; + } + if (c >= ASCII_CSI_IGNORE_FIRST && c <= ASCII_CSI_IGNORE_LAST) { + vc->vc_state = EScsiignore; + return; + } + + /* parameters done, handle the control char @c */ + + vc->vc_state = ESnormal; + + switch (vc->vc_priv) { + case EPdec: + csi_DEC(tty, vc, c); + return; + case EPecma: + csi_ECMA(tty, vc, c); + return; + default: + return; + } + case ESgetsubpars: /* ESC [ 38/48/58, subparameters expected */ + switch (c) { + case ':': case ';': if (vc->vc_npar < NPAR - 1) { vc->vc_npar++; From eb3b0d92c9c39890592cca6647601fe5c631efea Mon Sep 17 00:00:00 2001 From: Xin Zhao Date: Fri, 13 Feb 2026 16:50:39 +0800 Subject: [PATCH 25/35] tty: tty_port: add workqueue to flip TTY buffer On the embedded platform, certain critical data, such as IMU data, is transmitted through UART. The tty_flip_buffer_push() interface in the TTY layer uses system_dfl_wq to handle the flipping of the TTY buffer. Although the unbound workqueue can create new threads on demand and wake up the kworker thread on an idle CPU, it may be preempted by real-time tasks or other high-prio tasks. flush_to_ldisc() needs to wake up the relevant data handle thread. When executing __wake_up_common_lock(), it calls spin_lock_irqsave(), which does not disable preemption but disables migration in RT-Linux. This prevents the kworker thread from being migrated to other cores by CPU's balancing logic, resulting in long delays. The call trace is as follows: __wake_up_common_lock __wake_up ep_poll_callback __wake_up_common __wake_up_common_lock __wake_up n_tty_receive_buf_common n_tty_receive_buf2 tty_ldisc_receive_buf tty_port_default_receive_buf flush_to_ldisc In our system, the processing interval for each frame of IMU data transmitted via UART can experience significant jitter due to this issue. Instead of the expected 10 to 15 ms frame processing interval, we see spikes up to 30 to 35 ms. Moreover, in just one or two hours, there can be 2 to 3 occurrences of such high jitter, which is quite frequent. This jitter exceeds the software's tolerable limit of 20 ms. Introduce flip_wq in tty_port which can be set by tty_port_link_wq() or as default linked to default workqueue allocated when tty_register_driver(). The default workqueue is allocated with flag WQ_SYSFS, so that cpumask and nice can be set dynamically. The execution timing of tty_port_link_wq() is not clearly restricted. The newly added function tty_port_link_driver_wq() checks whether the flip_wq of the tty_port has already been assigned when linking the default tty_driver's workqueue to the port. After the user has set a custom workqueue for a certain tty_port using tty_port_link_wq(), the system will only use this custom workqueue, even if tty_driver does not have %TTY_DRIVER_NO_WORKQUEUE flag. When tty_port register device, flip_wq link operation is done by tty_port_link_driver_wq(), but for in-memory devices the link operation cannot cover all the cases. Although tty_port_install() is dedicated for in-memory devices lik PTY to link port allocated on demand, the logic of tty_port_install() is so simple that people may not call it, vc_cons[0].d->port is one such case. We check the buf.flip_wq when flip TTY buffer, if buf.flip_wq of TTY port is NULL, use system_dfl_wq as a backup. To avoid naming conflict of the default tty_driver's workqueue, using '"%s-%s", driver->name, driver->driver_name' as the workqueue name. In cases where driver_name is not specified and therefore is NULL, the workqueue is not created. Drivers that do not define driver_name are potentially in-memory devices like vty, which generally do not require special workqueue settings. Even with the combination of name and driver_name, the workqueue names can still be duplicated, as many tty serial drivers use "ttyS" as dev_name and "serial" as driver_name. I modified the conflicting driver_name of these drivers by appending a suffix of _xx based on the corresponding .c file. If this modification is not made, it could not only lead to duplicate workqueue names but also result in duplicate entries for the /proc/tty/driver/ nodes. Introduce %TTY_DRIVER_NO_WORKQUEUE flag meaning not to create the default single tty_driver workqueue. Two reasons why need to introduce the %TTY_DRIVER_NO_WORKQUEUE flag: 1. If the WQ_SYSFS parameter is enabled, workqueue_sysfs_register() will fail when trying to create a workqueue with the same name. The pty is an example of this; if both CONFIG_LEGACY_PTYS and CONFIG_UNIX98_PTYS are enabled, the call to tty_register_driver() in unix98_pty_init() will fail. 2. Different TTY ports may be used for different tasks, which may require separate core binding control via workqueues. In this case, the workqueue created by default in the TTY driver is unnecessary. Enabling this flag prevents the creation of this redundant workqueue. After applying this patch, we can set the related UART TTY flip buffer workqueue by sysfs. We set the cpumask to CPU cores associated with the IMU tasks, and set the nice to -20. Testing has shown significant improvement in the previously described issue, with almost no stuttering occurring anymore. Tested-by: Tommaso Merciai Tested-by: Marek Szyprowski Signed-off-by: Xin Zhao Link: https://patch.msgid.link/20260213085039.3274704-1-jackzxcui1989@163.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/pty.c | 12 ++++++++---- drivers/tty/serial/8250/8250_core.c | 2 +- drivers/tty/serial/apbuart.c | 2 +- drivers/tty/serial/dz.c | 2 +- drivers/tty/serial/ip22zilog.c | 2 +- drivers/tty/serial/zs.c | 2 +- drivers/tty/tty_buffer.c | 15 +++++++++++---- drivers/tty/tty_io.c | 25 ++++++++++++++++++++++++- drivers/tty/tty_port.c | 22 ++++++++++++++++++++++ include/linux/tty_buffer.h | 1 + include/linux/tty_driver.h | 7 +++++++ include/linux/tty_port.h | 13 +++++++++++++ 12 files changed, 91 insertions(+), 14 deletions(-) diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c index cb427e93372d..cc7f7091ed9a 100644 --- a/drivers/tty/pty.c +++ b/drivers/tty/pty.c @@ -532,14 +532,16 @@ static void __init legacy_pty_init(void) pty_driver = tty_alloc_driver(legacy_count, TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | - TTY_DRIVER_DYNAMIC_ALLOC); + TTY_DRIVER_DYNAMIC_ALLOC | + TTY_DRIVER_NO_WORKQUEUE); if (IS_ERR(pty_driver)) panic("Couldn't allocate pty driver"); pty_slave_driver = tty_alloc_driver(legacy_count, TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | - TTY_DRIVER_DYNAMIC_ALLOC); + TTY_DRIVER_DYNAMIC_ALLOC | + TTY_DRIVER_NO_WORKQUEUE); if (IS_ERR(pty_slave_driver)) panic("Couldn't allocate pty slave driver"); @@ -849,7 +851,8 @@ static void __init unix98_pty_init(void) TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_DEVPTS_MEM | - TTY_DRIVER_DYNAMIC_ALLOC); + TTY_DRIVER_DYNAMIC_ALLOC | + TTY_DRIVER_NO_WORKQUEUE); if (IS_ERR(ptm_driver)) panic("Couldn't allocate Unix98 ptm driver"); pts_driver = tty_alloc_driver(NR_UNIX98_PTY_MAX, @@ -857,7 +860,8 @@ static void __init unix98_pty_init(void) TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_DEVPTS_MEM | - TTY_DRIVER_DYNAMIC_ALLOC); + TTY_DRIVER_DYNAMIC_ALLOC | + TTY_DRIVER_NO_WORKQUEUE); if (IS_ERR(pts_driver)) panic("Couldn't allocate Unix98 pts driver"); diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index d2e2c5dfef99..a428e88938eb 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -524,7 +524,7 @@ console_initcall(univ8250_console_init); struct uart_driver serial8250_reg = { .owner = THIS_MODULE, - .driver_name = "serial", + .driver_name = "serial_8250", .dev_name = "ttyS", .major = TTY_MAJOR, .minor = 64, diff --git a/drivers/tty/serial/apbuart.c b/drivers/tty/serial/apbuart.c index 364599f256db..3e46341cfff8 100644 --- a/drivers/tty/serial/apbuart.c +++ b/drivers/tty/serial/apbuart.c @@ -505,7 +505,7 @@ console_initcall(apbuart_console_init); static struct uart_driver grlib_apbuart_driver = { .owner = THIS_MODULE, - .driver_name = "serial", + .driver_name = "serial_apbuart", .dev_name = "ttyS", .major = SERIAL_APBUART_MAJOR, .minor = SERIAL_APBUART_MINOR, diff --git a/drivers/tty/serial/dz.c b/drivers/tty/serial/dz.c index eba91daedef8..e53c54353c3e 100644 --- a/drivers/tty/serial/dz.c +++ b/drivers/tty/serial/dz.c @@ -914,7 +914,7 @@ console_initcall(dz_serial_console_init); static struct uart_driver dz_reg = { .owner = THIS_MODULE, - .driver_name = "serial", + .driver_name = "serial_dz", .dev_name = "ttyS", .major = TTY_MAJOR, .minor = 64, diff --git a/drivers/tty/serial/ip22zilog.c b/drivers/tty/serial/ip22zilog.c index 6e19c6713849..a69b06893d9e 100644 --- a/drivers/tty/serial/ip22zilog.c +++ b/drivers/tty/serial/ip22zilog.c @@ -1015,7 +1015,7 @@ static struct console ip22zilog_console = { static struct uart_driver ip22zilog_reg = { .owner = THIS_MODULE, - .driver_name = "serial", + .driver_name = "serial_ip22zilog", .dev_name = "ttyS", .major = TTY_MAJOR, .minor = 64, diff --git a/drivers/tty/serial/zs.c b/drivers/tty/serial/zs.c index 79ea7108a0f3..72a3c0d90f40 100644 --- a/drivers/tty/serial/zs.c +++ b/drivers/tty/serial/zs.c @@ -1252,7 +1252,7 @@ console_initcall(zs_serial_console_init); static struct uart_driver zs_reg = { .owner = THIS_MODULE, - .driver_name = "serial", + .driver_name = "serial_zs", .dev_name = "ttyS", .major = TTY_MAJOR, .minor = 64, diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index 79ec953824d5..96be90db53b7 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -59,6 +59,13 @@ void tty_buffer_lock_exclusive(struct tty_port *port) } EXPORT_SYMBOL_GPL(tty_buffer_lock_exclusive); +static bool tty_buffer_queue_work(struct tty_bufhead *buf) +{ + struct workqueue_struct *flip_wq = READ_ONCE(buf->flip_wq); + + return queue_work(flip_wq ?: system_dfl_wq, &buf->work); +} + /** * tty_buffer_unlock_exclusive - release exclusive access * @port: tty port owning the flip buffer @@ -76,7 +83,7 @@ void tty_buffer_unlock_exclusive(struct tty_port *port) mutex_unlock(&buf->lock); if (restart) - queue_work(system_dfl_wq, &buf->work); + tty_buffer_queue_work(buf); } EXPORT_SYMBOL_GPL(tty_buffer_unlock_exclusive); @@ -530,7 +537,7 @@ void tty_flip_buffer_push(struct tty_port *port) struct tty_bufhead *buf = &port->buf; tty_flip_buffer_commit(buf->tail); - queue_work(system_dfl_wq, &buf->work); + tty_buffer_queue_work(buf); } EXPORT_SYMBOL(tty_flip_buffer_push); @@ -560,7 +567,7 @@ int tty_insert_flip_string_and_push_buffer(struct tty_port *port, tty_flip_buffer_commit(buf->tail); spin_unlock_irqrestore(&port->lock, flags); - queue_work(system_dfl_wq, &buf->work); + tty_buffer_queue_work(buf); return size; } @@ -613,7 +620,7 @@ void tty_buffer_set_lock_subclass(struct tty_port *port) bool tty_buffer_restart_work(struct tty_port *port) { - return queue_work(system_dfl_wq, &port->buf.work); + return tty_buffer_queue_work(&port->buf); } bool tty_buffer_cancel_work(struct tty_port *port) diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index a5d0457e0e28..6b283fd03ff8 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -3443,10 +3443,27 @@ int tty_register_driver(struct tty_driver *driver) if (error < 0) goto err; + /* + * Drivers that do not define driver_name are potentially in-memory devices + * like vty, which generally do not require special workqueue settings. + */ + if (!(driver->flags & TTY_DRIVER_NO_WORKQUEUE) && driver->driver_name) { + driver->flip_wq = alloc_workqueue("%s-%s", WQ_UNBOUND | WQ_SYSFS, + 0, driver->name, driver->driver_name); + if (!driver->flip_wq) { + error = -ENOMEM; + goto err_unreg_char; + } + for (i = 0; i < driver->num; i++) { + if (driver->ports[i]) + tty_port_link_driver_wq(driver->ports[i], driver); + } + } + if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) { error = tty_cdev_add(driver, dev, 0, driver->num); if (error) - goto err_unreg_char; + goto err_destroy_wq; } scoped_guard(mutex, &tty_mutex) @@ -3472,6 +3489,10 @@ int tty_register_driver(struct tty_driver *driver) scoped_guard(mutex, &tty_mutex) list_del(&driver->tty_drivers); +err_destroy_wq: + if (driver->flip_wq) + destroy_workqueue(driver->flip_wq); + err_unreg_char: unregister_chrdev_region(dev, driver->num); err: @@ -3491,6 +3512,8 @@ void tty_unregister_driver(struct tty_driver *driver) driver->num); scoped_guard(mutex, &tty_mutex) list_del(&driver->tty_drivers); + if (driver->flip_wq) + destroy_workqueue(driver->flip_wq); } EXPORT_SYMBOL(tty_unregister_driver); diff --git a/drivers/tty/tty_port.c b/drivers/tty/tty_port.c index fe67c5cb0a3f..54359310e293 100644 --- a/drivers/tty/tty_port.c +++ b/drivers/tty/tty_port.c @@ -99,6 +99,23 @@ void tty_port_init(struct tty_port *port) } EXPORT_SYMBOL(tty_port_init); +/** + * tty_port_link_wq - link tty_port and flip workqueue + * @port: tty_port of the device + * @flip_wq: workqueue to queue flip buffer work on + * + * Whenever %TTY_DRIVER_NO_WORKQUEUE is used, every tty_port can be linked to + * a workqueue manually by this function. + * tty_port will use system_dfl_wq when buf.flip_wq is NULL. + * + * Note that tty_port API will NOT destroy the workqueue. + */ +void tty_port_link_wq(struct tty_port *port, struct workqueue_struct *flip_wq) +{ + port->buf.flip_wq = flip_wq; +} +EXPORT_SYMBOL_GPL(tty_port_link_wq); + /** * tty_port_link_device - link tty and tty_port * @port: tty_port of the device @@ -157,6 +174,7 @@ struct device *tty_port_register_device_attr(struct tty_port *port, const struct attribute_group **attr_grp) { tty_port_link_device(port, driver, index); + tty_port_link_driver_wq(port, driver); return tty_register_device_attr(driver, index, device, drvdata, attr_grp); } @@ -183,6 +201,7 @@ struct device *tty_port_register_device_attr_serdev(struct tty_port *port, struct device *dev; tty_port_link_device(port, driver, index); + tty_port_link_driver_wq(port, driver); dev = serdev_tty_port_register(port, host, parent, driver, index); if (PTR_ERR(dev) != -ENODEV) { @@ -210,6 +229,7 @@ void tty_port_unregister_device(struct tty_port *port, { int ret; + WRITE_ONCE(port->buf.flip_wq, NULL); ret = serdev_tty_port_unregister(port); if (ret == 0) return; @@ -257,6 +277,7 @@ void tty_port_destroy(struct tty_port *port) { tty_buffer_cancel_work(port); tty_buffer_free_all(port); + WRITE_ONCE(port->buf.flip_wq, NULL); } EXPORT_SYMBOL(tty_port_destroy); @@ -703,6 +724,7 @@ int tty_port_install(struct tty_port *port, struct tty_driver *driver, struct tty_struct *tty) { tty->port = port; + tty_port_link_driver_wq(port, driver); return tty_standard_install(driver, tty); } EXPORT_SYMBOL_GPL(tty_port_install); diff --git a/include/linux/tty_buffer.h b/include/linux/tty_buffer.h index 31125e3be3c5..48adcb0e8ff3 100644 --- a/include/linux/tty_buffer.h +++ b/include/linux/tty_buffer.h @@ -34,6 +34,7 @@ static inline u8 *flag_buf_ptr(struct tty_buffer *b, unsigned int ofs) struct tty_bufhead { struct tty_buffer *head; /* Queue head */ + struct workqueue_struct *flip_wq; struct work_struct work; struct mutex lock; atomic_t priority; diff --git a/include/linux/tty_driver.h b/include/linux/tty_driver.h index 188ee9b768eb..1f2896e56e77 100644 --- a/include/linux/tty_driver.h +++ b/include/linux/tty_driver.h @@ -69,6 +69,10 @@ struct serial_struct; * Do not create numbered ``/dev`` nodes. For example, create * ``/dev/ttyprintk`` and not ``/dev/ttyprintk0``. Applicable only when a * driver for a single tty device is being allocated. + * + * @TTY_DRIVER_NO_WORKQUEUE: + * Do not create workqueue when tty_register_driver(). Whenever set, flip + * buffer workqueue can be set by tty_port_link_wq() for every port. */ enum tty_driver_flag { TTY_DRIVER_INSTALLED = BIT(0), @@ -79,6 +83,7 @@ enum tty_driver_flag { TTY_DRIVER_HARDWARE_BREAK = BIT(5), TTY_DRIVER_DYNAMIC_ALLOC = BIT(6), TTY_DRIVER_UNNUMBERED_NODE = BIT(7), + TTY_DRIVER_NO_WORKQUEUE = BIT(8), }; enum tty_driver_type { @@ -506,6 +511,7 @@ struct tty_operations { * @flags: tty driver flags (%TTY_DRIVER_) * @proc_entry: proc fs entry, used internally * @other: driver of the linked tty; only used for the PTY driver + * @flip_wq: workqueue to queue flip buffer work on * @ttys: array of active &struct tty_struct, set by tty_standard_install() * @ports: array of &struct tty_port; can be set during initialization by * tty_port_link_device() and similar @@ -539,6 +545,7 @@ struct tty_driver { unsigned long flags; struct proc_dir_entry *proc_entry; struct tty_driver *other; + struct workqueue_struct *flip_wq; /* * Pointer to the tty data structures diff --git a/include/linux/tty_port.h b/include/linux/tty_port.h index 660c254f1efe..d2a7882c0b58 100644 --- a/include/linux/tty_port.h +++ b/include/linux/tty_port.h @@ -138,6 +138,7 @@ struct tty_port { kernel */ void tty_port_init(struct tty_port *port); +void tty_port_link_wq(struct tty_port *port, struct workqueue_struct *flip_wq); void tty_port_link_device(struct tty_port *port, struct tty_driver *driver, unsigned index); struct device *tty_port_register_device(struct tty_port *port, @@ -165,6 +166,18 @@ static inline struct tty_port *tty_port_get(struct tty_port *port) return NULL; } +/* + * Never overwrite the workqueue set by tty_port_link_wq(). + * No effect when %TTY_DRIVER_NO_WORKQUEUE is set, as driver->flip_wq is + * %NULL. + */ +static inline void tty_port_link_driver_wq(struct tty_port *port, + struct tty_driver *driver) +{ + if (!port->buf.flip_wq) + tty_port_link_wq(port, driver->flip_wq); +} + /* If the cts flow control is enabled, return true. */ static inline bool tty_port_cts_enabled(const struct tty_port *port) { From c8f3ac729f827b655e0a239a3967b47c9dfce606 Mon Sep 17 00:00:00 2001 From: Ravi Rama Date: Fri, 13 Mar 2026 14:47:27 -0500 Subject: [PATCH 26/35] serial: 8250_fintek: Add support for F81214E The F81214E is a LPC/eSPI to 2 UART Super I/O chip. Functionally, it is the same as the F81216E. The only difference is that the F81216E has 4 UART ports, whereas the F81214E has 2 UART ports. Signed-off-by: Ravi Rama Link: https://patch.msgid.link/20260313194731.2671-1-ravi.rama@nexthop.ai Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_fintek.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_fintek.c b/drivers/tty/serial/8250/8250_fintek.c index b4461a89b8d0..976c5748905c 100644 --- a/drivers/tty/serial/8250/8250_fintek.c +++ b/drivers/tty/serial/8250/8250_fintek.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Probe for F81216A LPC to 4 UART + * Probe for F81216A LPC to 4 UART and F81214E LPC/eSPI to 2 UART * * Copyright (C) 2014-2016 Ricardo Ribalda, Qtechnology A/S */ @@ -23,6 +23,7 @@ #define CHIP_ID_F81216AD 0x1602 #define CHIP_ID_F81216E 0x1617 #define CHIP_ID_F81216H 0x0501 +#define CHIP_ID_F81214E 0x1417 #define CHIP_ID_F81216 0x0802 #define VENDOR_ID1 0x23 #define VENDOR_ID1_VAL 0x19 @@ -161,6 +162,7 @@ static int fintek_8250_check_id(struct fintek_8250 *pdata) case CHIP_ID_F81216AD: case CHIP_ID_F81216E: case CHIP_ID_F81216H: + case CHIP_ID_F81214E: case CHIP_ID_F81216: break; default: @@ -185,6 +187,7 @@ static int fintek_8250_get_ldn_range(struct fintek_8250 *pdata, int *min, case CHIP_ID_F81216AD: case CHIP_ID_F81216E: case CHIP_ID_F81216H: + case CHIP_ID_F81214E: case CHIP_ID_F81216: *min = F81216_LDN_LOW; *max = F81216_LDN_HIGH; @@ -255,6 +258,7 @@ static void fintek_8250_set_irq_mode(struct fintek_8250 *pdata, bool is_level) case CHIP_ID_F81216AD: case CHIP_ID_F81216E: case CHIP_ID_F81216H: + case CHIP_ID_F81214E: case CHIP_ID_F81216: sio_write_mask_reg(pdata, FINTEK_IRQ_MODE, IRQ_SHARE, IRQ_SHARE); @@ -269,6 +273,7 @@ static void fintek_8250_set_max_fifo(struct fintek_8250 *pdata) switch (pdata->pid) { case CHIP_ID_F81216E: /* 128Bytes FIFO */ case CHIP_ID_F81216H: + case CHIP_ID_F81214E: case CHIP_ID_F81966: case CHIP_ID_F81866: sio_write_mask_reg(pdata, FIFO_CTRL, @@ -304,6 +309,7 @@ static void fintek_8250_set_termios(struct uart_port *port, switch (pdata->pid) { case CHIP_ID_F81216E: case CHIP_ID_F81216H: + case CHIP_ID_F81214E: reg = RS485; break; case CHIP_ID_F81966: @@ -354,6 +360,7 @@ static void fintek_8250_set_termios_handler(struct uart_8250_port *uart) switch (pdata->pid) { case CHIP_ID_F81216E: case CHIP_ID_F81216H: + case CHIP_ID_F81214E: case CHIP_ID_F81966: case CHIP_ID_F81866: uart->port.set_termios = fintek_8250_set_termios; @@ -446,6 +453,7 @@ static void fintek_8250_set_rs485_handler(struct uart_8250_port *uart) break; case CHIP_ID_F81216E: /* F81216E does not support RS485 delays */ + case CHIP_ID_F81214E: /* F81214E does not support RS485 delays */ uart->port.rs485_config = fintek_8250_rs485_config; uart->port.rs485_supported = fintek_8250_rs485_supported; break; From 17fb51a90efc0b0f756c52bed241d17cfedd0cab Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Mon, 16 Mar 2026 02:42:56 +0800 Subject: [PATCH 27/35] dt-bindings: serial: 8250: Add Loongson 3A4000 uart compatible The UART controller on Loongson 3A4000 is compatible with Loongson 2K1500, which is NS16550A-compatible with an additional fractional frequency divisor register. Add loongson,ls3a4000-uart as compatible with loongson,ls2k1500-uart. Acked-by: Krzysztof Kozlowski Signed-off-by: Rong Zhang Reviewed-by: Jiaxun Yang Link: https://patch.msgid.link/20260315184301.412844-2-rongrong@oss.cipunited.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/serial/8250.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/serial/8250.yaml b/Documentation/devicetree/bindings/serial/8250.yaml index 73851f19330d..1d1f2a22776c 100644 --- a/Documentation/devicetree/bindings/serial/8250.yaml +++ b/Documentation/devicetree/bindings/serial/8250.yaml @@ -179,6 +179,7 @@ properties: - const: ns16550a - items: - enum: + - loongson,ls3a4000-uart - loongson,ls3a5000-uart - loongson,ls3a6000-uart - loongson,ls2k2000-uart From 502c6b95b8b7827ddcaca38a5befb3b68c55de01 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Mon, 16 Mar 2026 02:42:57 +0800 Subject: [PATCH 28/35] serial: 8250: loongson: Enable building on MIPS Loongson64 Loongson 3A4000 is a MIPS-based Loongson64 CPU which also supports 8250_loongson (loongson-uart). Enable building on MIPS Loongson64 so that Loongson 3A4000 can benefit from it. Signed-off-by: Rong Zhang Reviewed-by: Jiaxun Yang Link: https://patch.msgid.link/20260315184301.412844-3-rongrong@oss.cipunited.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/Kconfig | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig index fd4e8b6ab60d..fc3e58d62233 100644 --- a/drivers/tty/serial/8250/Kconfig +++ b/drivers/tty/serial/8250/Kconfig @@ -465,11 +465,12 @@ config SERIAL_8250_OMAP_TTYO_FIXUP config SERIAL_8250_LOONGSON tristate "Loongson 8250 based serial port" depends on SERIAL_8250 - depends on LOONGARCH || COMPILE_TEST + depends on LOONGARCH || MACH_LOONGSON64 || COMPILE_TEST help - If you have a machine based on LoongArch CPU you can enable - its onboard serial ports by enabling this option. The option - is applicable to both devicetree and ACPI, say Y to this option. + If you have a machine based on LoongArch CPU or MIPS-based Loongson + 3A4000 CPU you can enable its onboard serial ports by enabling this + option. The option is applicable to both devicetree and ACPI, say Y + to enable this option. If unsure, say N. config SERIAL_8250_LPC18XX From 2d2640712455fc1c9a0bab0196404e27cf48d1af Mon Sep 17 00:00:00 2001 From: Kathiravan Thirumoorthy Date: Thu, 19 Mar 2026 15:18:08 +0530 Subject: [PATCH 29/35] serial: qcom-geni: drop stray newline format specifier Drop the newline character from the middle of the printk message. This avoids breaking the message into two lines unnecessarily. Signed-off-by: Kathiravan Thirumoorthy Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260319-drop_stray_n-v1-1-37fb619538bb@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 9854bb2406e3..b365dd5da3cb 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -1282,7 +1282,7 @@ static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud) return -EINVAL; } - dev_dbg(port->se.dev, "desired_rate = %u, clk_rate = %lu, clk_div = %u\n, clk_idx = %u\n", + dev_dbg(port->se.dev, "desired_rate = %u, clk_rate = %lu, clk_div = %u, clk_idx = %u\n", baud * sampling_rate, clk_rate, clk_div, clk_idx); uport->uartclk = clk_rate; From da6dbbf11c01aba88233c5be247ed2918183b387 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 20 Mar 2026 23:08:27 +0100 Subject: [PATCH 30/35] serial: xilinx_uartps: Drop unused include This driver includes the legacy header but does not use any symbols from it. Drop the inclusion. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260320220827.3237499-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/xilinx_uartps.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/tty/serial/xilinx_uartps.c b/drivers/tty/serial/xilinx_uartps.c index c593d20a1b5b..a072b75dbaf2 100644 --- a/drivers/tty/serial/xilinx_uartps.c +++ b/drivers/tty/serial/xilinx_uartps.c @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include From fdb19f4ede3bf41c48721889114441389d1ad403 Mon Sep 17 00:00:00 2001 From: Kexin Sun Date: Tue, 24 Mar 2026 10:48:57 +0800 Subject: [PATCH 31/35] tty: atmel_serial: update outdated reference to atmel_tasklet_func() The modem-status comparison that used irq_status_prev was moved from atmel_tasklet_func() into atmel_handle_status() in commit d033e82db9a5 ("tty/serial: at91: handle IRQ status more safely"). Update the comment accordingly. Assisted-by: unnamed:deepseek-v3.2 coccinelle Signed-off-by: Kexin Sun Link: https://patch.msgid.link/20260324024857.3244-1-kexinsun@smail.nju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/atmel_serial.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index 08dd8f887956..5d8c1cfc1c60 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -1927,7 +1927,7 @@ static int atmel_startup(struct uart_port *port) atmel_uart_writel(port, ATMEL_US_FMR, fmr); } - /* Save current CSR for comparison in atmel_tasklet_func() */ + /* Save current CSR for comparison in atmel_handle_status() */ atmel_port->irq_status_prev = atmel_uart_readl(port, ATMEL_US_CSR); /* From 6672462c97ed29f1cf04317663ae0bffff261c3b Mon Sep 17 00:00:00 2001 From: Biju Das Date: Thu, 12 Mar 2026 08:26:58 +0000 Subject: [PATCH 32/35] dt-bindings: serial: renesas,rsci: Document RZ/G3L SoC Document the serial communication interface (RSCI) used on the Renesas RZ/G3L (R9A08G046) SoC. This SoC integrates the same RSCI IP block as the RZ/G3E (R9A09G047), but it has 3 clocks compared to 6 clocks on the RZ/G3E SoC. The RZ/G3L has a single TCLK with internal dividers, whereas the RZ/G3E has explicit clocks for TCLK and its dividers. Signed-off-by: Biju Das Reviewed-by: Geert Uytterhoeven Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260312082708.98835-2-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/serial/renesas,rsci.yaml | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Documentation/devicetree/bindings/serial/renesas,rsci.yaml b/Documentation/devicetree/bindings/serial/renesas,rsci.yaml index e059b14775eb..85ebb3056066 100644 --- a/Documentation/devicetree/bindings/serial/renesas,rsci.yaml +++ b/Documentation/devicetree/bindings/serial/renesas,rsci.yaml @@ -14,6 +14,7 @@ properties: compatible: oneOf: - enum: + - renesas,r9a08g046-rsci # RZ/G3L - renesas,r9a09g047-rsci # RZ/G3E - renesas,r9a09g077-rsci # RZ/T2H @@ -145,6 +146,31 @@ allOf: - resets - reset-names + - if: + properties: + compatible: + contains: + const: renesas,r9a08g046-rsci + then: + properties: + interrupts: + minItems: 6 + + interrupt-names: + minItems: 6 + + clocks: + minItems: 2 + maxItems: 3 + + clock-names: + minItems: 2 + maxItems: 3 + + required: + - resets + - reset-names + unevaluatedProperties: false examples: From 8f18d3cbd92065146147e958afa912ca94a237b0 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Thu, 12 Mar 2026 08:26:59 +0000 Subject: [PATCH 33/35] serial: sh-sci: Add support for RZ/G3L RSCI Add support for RZ/G3L RSCI. The RSCI IP found on the RZ/G3L SoC is similar to RZ/G3E, but it has 3 clocks (2 module clocks + 1 external clock) instead of 6 clocks (5 module clocks + 1 external clock) on the RZ/G3E. Both RZ/G3L and RZ/G3E have a 32-bit FIFO, but RZ/G3L has a single TCLK with internal dividers, whereas the RZ/G3E has explicit clocks for TCLK and its dividers. Add a new port type RSCI_PORT_SCIF32_SINGLE_TCLK to handle this clock difference. Signed-off-by: Biju Das Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260312082708.98835-3-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/rsci.c | 13 +++++++++++++ drivers/tty/serial/rsci.h | 1 + drivers/tty/serial/sh-sci-common.h | 1 + drivers/tty/serial/sh-sci.c | 14 +++++++++++--- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/rsci.c b/drivers/tty/serial/rsci.c index c3f12df693ad..b00c9e385169 100644 --- a/drivers/tty/serial/rsci.c +++ b/drivers/tty/serial/rsci.c @@ -695,6 +695,13 @@ struct sci_of_data of_rsci_rzg3e_data = { .params = &rsci_rzg3e_port_params, }; +struct sci_of_data of_rsci_rzg3l_data = { + .type = RSCI_PORT_SCIF32_SINGLE_TCLK, + .ops = &rsci_port_ops, + .uart_ops = &rsci_uart_ops, + .params = &rsci_rzg3e_port_params, +}; + struct sci_of_data of_rsci_rzt2h_data = { .type = RSCI_PORT_SCIF16, .ops = &rsci_port_ops, @@ -703,6 +710,11 @@ struct sci_of_data of_rsci_rzt2h_data = { }; #ifdef CONFIG_SERIAL_SH_SCI_EARLYCON +static int __init rsci_rzg3l_early_console_setup(struct earlycon_device *device, + const char *opt) +{ + return scix_early_console_setup(device, &of_rsci_rzg3l_data); +} static int __init rsci_rzg3e_early_console_setup(struct earlycon_device *device, const char *opt) @@ -716,6 +728,7 @@ static int __init rsci_rzt2h_early_console_setup(struct earlycon_device *device, return scix_early_console_setup(device, &of_rsci_rzt2h_data); } +OF_EARLYCON_DECLARE(rsci, "renesas,r9a08g046-rsci", rsci_rzg3l_early_console_setup); OF_EARLYCON_DECLARE(rsci, "renesas,r9a09g047-rsci", rsci_rzg3e_early_console_setup); OF_EARLYCON_DECLARE(rsci, "renesas,r9a09g077-rsci", rsci_rzt2h_early_console_setup); diff --git a/drivers/tty/serial/rsci.h b/drivers/tty/serial/rsci.h index 2aa2ba3973ee..0985fd1b3348 100644 --- a/drivers/tty/serial/rsci.h +++ b/drivers/tty/serial/rsci.h @@ -6,6 +6,7 @@ #include "sh-sci-common.h" extern struct sci_of_data of_rsci_rzg3e_data; +extern struct sci_of_data of_rsci_rzg3l_data; extern struct sci_of_data of_rsci_rzt2h_data; #endif /* __RSCI_H__ */ diff --git a/drivers/tty/serial/sh-sci-common.h b/drivers/tty/serial/sh-sci-common.h index f363a659c46a..01ff9fced803 100644 --- a/drivers/tty/serial/sh-sci-common.h +++ b/drivers/tty/serial/sh-sci-common.h @@ -9,6 +9,7 @@ enum SCI_PORT_TYPE { RSCI_PORT_SCIF16 = BIT(7) | 0, RSCI_PORT_SCIF32 = BIT(7) | 1, + RSCI_PORT_SCIF32_SINGLE_TCLK = BIT(7) | 2, }; enum SCI_CLKS { diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c index bd7486315338..6c819b6b2425 100644 --- a/drivers/tty/serial/sh-sci.c +++ b/drivers/tty/serial/sh-sci.c @@ -1184,7 +1184,8 @@ static int sci_handle_errors(struct uart_port *port) static bool sci_is_rsci_type(u8 type) { - return (type == RSCI_PORT_SCIF16 || type == RSCI_PORT_SCIF32); + return (type == RSCI_PORT_SCIF16 || type == RSCI_PORT_SCIF32 || + type == RSCI_PORT_SCIF32_SINGLE_TCLK); } static int sci_handle_fifo_overrun(struct uart_port *port) @@ -3181,7 +3182,8 @@ static int sci_init_clocks(struct sci_port *sci_port, struct device *dev) if (sci_port->type == PORT_HSCIF) { clk_names[SCI_SCK] = "hsck"; - } else if (sci_port->type == RSCI_PORT_SCIF16) { + } else if (sci_port->type == RSCI_PORT_SCIF16 || + sci_port->type == RSCI_PORT_SCIF32_SINGLE_TCLK) { clk_names[SCI_FCK] = "operation"; clk_names[SCI_BRG_INT] = "bus"; } else if (sci_port->type == RSCI_PORT_SCIF32) { @@ -3196,7 +3198,8 @@ static int sci_init_clocks(struct sci_port *sci_port, struct device *dev) if (IS_ERR(clk)) return PTR_ERR(clk); - if (!clk && sci_port->type == RSCI_PORT_SCIF16 && + if (!clk && (sci_port->type == RSCI_PORT_SCIF16 || + sci_port->type == RSCI_PORT_SCIF32_SINGLE_TCLK) && (i == SCI_FCK || i == SCI_BRG_INT)) return dev_err_probe(dev, -ENODEV, "failed to get %s\n", name); @@ -3330,6 +3333,7 @@ static int sci_init_single(struct platform_device *dev, break; case PORT_SCIFA: case RSCI_PORT_SCIF32: + case RSCI_PORT_SCIF32_SINGLE_TCLK: sci_port->rx_trigger = 32; break; case PORT_SCIF: @@ -3663,6 +3667,10 @@ static const struct of_device_id of_sci_match[] __maybe_unused = { .data = &of_sci_scif_rzv2h, }, #ifdef CONFIG_SERIAL_RSCI + { + .compatible = "renesas,r9a08g046-rsci", + .data = &of_rsci_rzg3l_data, + }, { .compatible = "renesas,r9a09g047-rsci", .data = &of_rsci_rzg3e_data, From d50dd728ced93a1900ff0be924b6f273baf59fb2 Mon Sep 17 00:00:00 2001 From: Jason Andryuk Date: Wed, 18 Mar 2026 19:53:26 -0400 Subject: [PATCH 34/35] hvc/xen: Check console connection flag When the console out buffer is filled, __write_console() will return 0 as it cannot send any data. domU_write_console() will then spin in `while (len)` as len doesn't decrement until xenconsoled attaches. This would block a domU and nullify the parallelism of Hyperlaunch until dom0 userspace starts xenconsoled, which empties the buffer. Xen 4.21 added a connection field to the xen console page. This is set to XENCONSOLE_DISCONNECTED (1) when a domain is built, and xenconsoled will set it to XENCONSOLE_CONNECTED (0) when it connects. Update the hvc_xen driver to check the field. When the field is disconnected, drop the write with -ENOTCONN. We only drop the write when the field is XENCONSOLE_DISCONNECTED (1) to try for maximum compatibility. The Xen toolstack has historically zero initialized the console, so it should see XENCONSOLE_CONNECTED (0) by default. If an implemenation used uninitialized memory, only checking for XENCONSOLE_DISCONNECTED could have the lowest chance of not connecting. This lets the hyperlaunched domU boot without stalling. Once dom0 starts xenconsoled, xl console can be used to access the domU's hvc0. Paritally sync console.h from xen.git to bring in the new field. Reviewed-by: Stefano Stabellini Signed-off-by: Jason Andryuk Link: https://patch.msgid.link/20260318235326.14568-1-jason.andryuk@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/hvc/hvc_xen.c | 3 +++ include/xen/interface/io/console.h | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/drivers/tty/hvc/hvc_xen.c b/drivers/tty/hvc/hvc_xen.c index 7f0b6262488c..c407592442cd 100644 --- a/drivers/tty/hvc/hvc_xen.c +++ b/drivers/tty/hvc/hvc_xen.c @@ -139,6 +139,9 @@ static ssize_t domU_write_console(uint32_t vtermno, const u8 *data, size_t len) if (cons == NULL) return -EINVAL; + if (cons->intf->connection == XENCONSOLE_DISCONNECTED) + return -ENOTCONN; + /* * Make sure the whole buffer is emitted, polling if * necessary. We don't ever want to rely on the hvc daemon diff --git a/include/xen/interface/io/console.h b/include/xen/interface/io/console.h index cf17e89ed861..687949bdebb1 100644 --- a/include/xen/interface/io/console.h +++ b/include/xen/interface/io/console.h @@ -19,6 +19,19 @@ struct xencons_interface { char out[2048]; XENCONS_RING_IDX in_cons, in_prod; XENCONS_RING_IDX out_cons, out_prod; +/* + * Flag values signaling from backend to frontend whether the console is + * connected. i.e. Whether it will be serviced and emptied. + * + * The flag starts as disconnected. + */ +#define XENCONSOLE_DISCONNECTED 1 +/* + * The flag is set to connected when the backend connects and the console + * will be serviced. + */ +#define XENCONSOLE_CONNECTED 0 + uint8_t connection; }; #endif /* __XEN_PUBLIC_IO_CONSOLE_H__ */ From a1a81aef99e853dec84241d701fbf587d713eb5b Mon Sep 17 00:00:00 2001 From: Thomas Bogendoerfer Date: Thu, 2 Apr 2026 12:21:53 +0200 Subject: [PATCH 35/35] tty: serial: ip22zilog: Fix section mispatch warning ip22zilog_prepare() is now called by driver probe routine, so it shouldn't be in the __init section any longer. Fixes: 3fc36ae6abd2 ("tty: serial: ip22zilog: Use platform device for probing") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202604020945.c9jAvCPs-lkp@intel.com/ Signed-off-by: Thomas Bogendoerfer Link: https://patch.msgid.link/20260402102154.136620-1-tbogendoerfer@suse.de Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/ip22zilog.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/ip22zilog.c b/drivers/tty/serial/ip22zilog.c index a69b06893d9e..3fb8fdf8a853 100644 --- a/drivers/tty/serial/ip22zilog.c +++ b/drivers/tty/serial/ip22zilog.c @@ -1025,7 +1025,7 @@ static struct uart_driver ip22zilog_reg = { #endif }; -static void __init ip22zilog_prepare(struct uart_ip22zilog_port *up) +static void ip22zilog_prepare(struct uart_ip22zilog_port *up) { unsigned char sysrq_on = IS_ENABLED(CONFIG_SERIAL_IP22_ZILOG_CONSOLE); int brg;