[PATCH v5 10/12] serial: 8250: add Ambarella UART driver
From: Long Zhao via B4 Relay
Date: Tue Aug 18 2026 - 06:44:17 EST
From: Long Zhao <longzhao@xxxxxxxxxxxxx>
Add an 8250 platform driver for Ambarella CV75 instead of claiming
snps,dw-apb-uart compatibility.
Signed-off-by: Long Zhao <longzhao@xxxxxxxxxxxxx>
---
drivers/tty/serial/8250/8250_ambarella.c | 312 +++++++++++++++++++++++++++++++
drivers/tty/serial/8250/Kconfig | 9 +
drivers/tty/serial/8250/Makefile | 1 +
3 files changed, 322 insertions(+)
diff --git a/drivers/tty/serial/8250/8250_ambarella.c b/drivers/tty/serial/8250/8250_ambarella.c
new file mode 100644
index 000000000000..951355e19fa2
--- /dev/null
+++ b/drivers/tty/serial/8250/8250_ambarella.c
@@ -0,0 +1,312 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Serial port driver for Ambarella UART
+ *
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/serial_8250.h>
+#include <linux/serial_reg.h>
+
+#include "8250.h"
+
+#define AMBARELLA_UART_IER_ETOI BIT(5)
+#define AMBARELLA_UART_USR 0x1f
+#define AMBARELLA_UART_USR_BUSY BIT(0)
+#define AMBARELLA_UART_IIR_IID GENMASK(3, 0)
+#define AMBARELLA_UART_IIR_STATUS GENMASK(5, 0)
+
+struct ambarella_uart_soc_data {
+ unsigned int fifo_size;
+};
+
+struct ambarella_uart {
+ int line;
+ struct clk *clk;
+ const struct ambarella_uart_soc_data *soc_data;
+ bool in_idle;
+};
+
+static void ambarella_idle_exit(struct uart_port *p)
+{
+ struct ambarella_uart *data = p->private_data;
+ struct uart_8250_port *up = up_to_u8250p(p);
+
+ if (up->capabilities & UART_CAP_FIFO)
+ writel(up->fcr, p->membase + (UART_FCR << p->regshift));
+ writel(up->mcr, p->membase + (UART_MCR << p->regshift));
+ writel(up->ier, p->membase + (UART_IER << p->regshift));
+
+ data->in_idle = false;
+}
+
+static int ambarella_idle_enter(struct uart_port *p)
+{
+ struct ambarella_uart *data = p->private_data;
+ struct uart_8250_port *up = up_to_u8250p(p);
+ unsigned int status;
+ int retries = 4;
+
+ lockdep_assert_held_once(&p->lock);
+
+ data->in_idle = true;
+ writel(0, p->membase + (UART_IER << p->regshift));
+
+ serial8250_fifo_wait_for_lsr_thre(up, p->fifosize);
+ ndelay(p->frame_time);
+ writel(up->mcr | UART_MCR_LOOP,
+ p->membase + (UART_MCR << p->regshift));
+
+ do {
+ serial8250_clear_fifos(up);
+ if (!(readl(p->membase +
+ (AMBARELLA_UART_USR << p->regshift)) &
+ AMBARELLA_UART_USR_BUSY))
+ break;
+ ndelay(p->frame_time);
+ } while (--retries);
+
+ status = serial_lsr_in(up);
+ if (status & UART_LSR_DR) {
+ readl(p->membase + (UART_RX << p->regshift));
+ up->lsr_saved_flags = 0;
+ }
+
+ if (readl(p->membase + (AMBARELLA_UART_USR << p->regshift)) &
+ AMBARELLA_UART_USR_BUSY) {
+ ambarella_idle_exit(p);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static void ambarella_serial_out(struct uart_port *p, unsigned int offset,
+ u32 value)
+{
+ struct ambarella_uart *data = p->private_data;
+ u32 lcr;
+
+ if (offset != UART_LCR || !data || data->in_idle) {
+ writel(value, p->membase + (offset << p->regshift));
+ return;
+ }
+
+ lcr = readl(p->membase + (UART_LCR << p->regshift));
+ if (lcr == value)
+ return;
+
+ writel(value, p->membase + (UART_LCR << p->regshift));
+ lcr = readl(p->membase + (UART_LCR << p->regshift));
+ if ((lcr & ~UART_LCR_SPAR) == (value & ~UART_LCR_SPAR))
+ return;
+
+ if (ambarella_idle_enter(p))
+ return;
+
+ writel(value, p->membase + (UART_LCR << p->regshift));
+ ambarella_idle_exit(p);
+}
+
+static u32 ambarella_serial_in(struct uart_port *p, unsigned int offset)
+{
+ return readl(p->membase + (offset << p->regshift));
+}
+
+static int ambarella_handle_irq(struct uart_port *p)
+{
+ struct uart_8250_port *up = up_to_u8250p(p);
+ unsigned int iir = serial_port_in(p, UART_IIR);
+ bool rx_timeout = (iir & AMBARELLA_UART_IIR_STATUS) ==
+ UART_IIR_RX_TIMEOUT;
+ unsigned int status;
+
+ guard(uart_port_lock_check_sysrq_irqsave)(p);
+
+ switch (FIELD_GET(AMBARELLA_UART_IIR_IID, iir)) {
+ case UART_IIR_NO_INT:
+ return 0;
+ case UART_IIR_BUSY:
+ serial_port_in(p, AMBARELLA_UART_USR);
+ return 1;
+ }
+
+ if (rx_timeout) {
+ status = serial_lsr_in(up);
+ if (!(status & (UART_LSR_DR | UART_LSR_BI)))
+ serial_port_in(p, UART_RX);
+ }
+
+ serial8250_handle_irq_locked(p, iir);
+ return 1;
+}
+
+static void ambarella_set_divisor(struct uart_port *p, unsigned int baud,
+ unsigned int quot, unsigned int quot_frac)
+{
+ struct uart_8250_port *up = up_to_u8250p(p);
+
+ if (ambarella_idle_enter(p))
+ return;
+
+ serial_port_out(p, UART_LCR, up->lcr | UART_LCR_DLAB);
+ if (serial_port_in(p, UART_LCR) & UART_LCR_DLAB)
+ serial_dl_write(up, quot);
+ serial_port_out(p, UART_LCR, up->lcr);
+
+ ambarella_idle_exit(p);
+}
+
+static int ambarella_startup(struct uart_port *port)
+{
+ struct uart_8250_port *up = up_to_u8250p(port);
+ int ret;
+
+ ret = serial8250_do_startup(port);
+ if (ret)
+ return ret;
+
+ up->ier |= AMBARELLA_UART_IER_ETOI;
+ serial_port_out(port, UART_IER, up->ier);
+
+ return 0;
+}
+
+static int ambarella_uart_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ambarella_uart *data;
+ struct uart_8250_port uart = {};
+ struct resource *regs;
+ u32 reg_io_width = 4;
+ u32 reg_shift = 2;
+ int irq, ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!regs)
+ return dev_err_probe(dev, -EINVAL, "missing registers\n");
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->soc_data = device_get_match_data(dev);
+ if (!data->soc_data)
+ return -EINVAL;
+
+ data->clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(data->clk))
+ return dev_err_probe(dev, PTR_ERR(data->clk),
+ "unable to get clock\n");
+
+ uart.port.dev = dev;
+ uart.port.mapbase = regs->start;
+ uart.port.mapsize = resource_size(regs);
+ uart.port.membase = devm_ioremap(dev, regs->start,
+ resource_size(regs));
+ if (!uart.port.membase)
+ return -ENOMEM;
+
+ uart.port.irq = irq;
+ uart.port.type = PORT_16550A;
+ uart.port.flags = UPF_FIXED_PORT | UPF_FIXED_TYPE | UPF_SKIP_TEST;
+ uart.port.startup = ambarella_startup;
+ uart.port.handle_irq = ambarella_handle_irq;
+ uart.port.set_divisor = ambarella_set_divisor;
+ uart.port.uartclk = clk_get_rate(data->clk);
+ if (!uart.port.uartclk)
+ return dev_err_probe(dev, -EINVAL, "clock rate not defined\n");
+
+ uart.port.fifosize = data->soc_data->fifo_size;
+ uart.port.private_data = data;
+ uart.capabilities = UART_CAP_FIFO | UART_CAP_NOTEMT | UART_CAP_AFE;
+
+ of_property_read_u32(dev->of_node, "reg-shift", ®_shift);
+ of_property_read_u32(dev->of_node, "reg-io-width", ®_io_width);
+ uart.port.regshift = reg_shift;
+
+ switch (reg_io_width) {
+ case 4:
+ uart.port.iotype = UPIO_MEM32;
+ uart.port.serial_in = ambarella_serial_in;
+ uart.port.serial_out = ambarella_serial_out;
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "unsupported reg-io-width %u\n",
+ reg_io_width);
+ }
+
+ ret = serial8250_register_8250_port(&uart);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "unable to register 8250 port\n");
+
+ data->line = ret;
+ platform_set_drvdata(pdev, data);
+ return 0;
+}
+
+static void ambarella_uart_remove(struct platform_device *pdev)
+{
+ struct ambarella_uart *data = platform_get_drvdata(pdev);
+
+ serial8250_unregister_port(data->line);
+}
+
+static const struct ambarella_uart_soc_data cv75_uart_data = {
+ .fifo_size = 64,
+};
+
+static const struct of_device_id ambarella_uart_dt_ids[] = {
+ {
+ .compatible = "ambarella,cv75-uart",
+ .data = &cv75_uart_data,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ambarella_uart_dt_ids);
+
+static struct platform_driver ambarella_uart_platform_driver = {
+ .driver = {
+ .name = "8250-ambarella",
+ .of_match_table = ambarella_uart_dt_ids,
+ },
+ .probe = ambarella_uart_probe,
+ .remove = ambarella_uart_remove,
+};
+
+module_platform_driver(ambarella_uart_platform_driver);
+
+static int __init ambarella_early_setup(struct earlycon_device *device,
+ const char *options)
+{
+ struct uart_port *port = &device->port;
+
+ if (!port->membase)
+ return -ENODEV;
+
+ port->iotype = UPIO_MEM32;
+ port->regshift = 2;
+ port->serial_in = ambarella_serial_in;
+ port->serial_out = ambarella_serial_out;
+
+ return early_serial8250_setup(device, options);
+}
+
+OF_EARLYCON_DECLARE(ambarella, "ambarella,cv75-uart", ambarella_early_setup);
+
+MODULE_IMPORT_NS("SERIAL_8250");
+MODULE_AUTHOR("Long Zhao <longzhao@xxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("Ambarella UART driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig
index fc3e58d62233..523c55be3227 100644
--- a/drivers/tty/serial/8250/Kconfig
+++ b/drivers/tty/serial/8250/Kconfig
@@ -337,6 +337,15 @@ config SERIAL_8250_ACORN
system, say Y to this option. The driver can handle 1, 2, or 3 port
cards. If unsure, say N.
+config SERIAL_8250_AMBARELLA
+ tristate "Ambarella UART support"
+ depends on SERIAL_8250 && HAVE_CLK
+ depends on ARCH_AMBARELLA || COMPILE_TEST
+ default y if ARCH_AMBARELLA
+ help
+ Support for the on-chip UART found on Ambarella SoCs.
+ If unsure, say N.
+
config SERIAL_8250_BCM2835AUX
tristate "BCM2835 auxiliar mini UART support"
depends on ARCH_BCM2835 || COMPILE_TEST
diff --git a/drivers/tty/serial/8250/Makefile b/drivers/tty/serial/8250/Makefile
index 6d21402b4435..601d9e2589fe 100644
--- a/drivers/tty/serial/8250/Makefile
+++ b/drivers/tty/serial/8250/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_SERIAL_8250_CONSOLE) += 8250_early.o
obj-$(CONFIG_SERIAL_8250_ACCENT) += 8250_accent.o
obj-$(CONFIG_SERIAL_8250_ACORN) += 8250_acorn.o
+obj-$(CONFIG_SERIAL_8250_AMBARELLA) += 8250_ambarella.o
obj-$(CONFIG_SERIAL_8250_ASPEED_VUART) += 8250_aspeed_vuart.o
obj-$(CONFIG_SERIAL_8250_BCM2835AUX) += 8250_bcm2835aux.o
obj-$(CONFIG_SERIAL_8250_BCM7271) += 8250_bcm7271.o
--
2.34.1