[PATCH v5 10/12] serial: 8250: add Ambarella UART driver

Long Zhao via B4 Relay <[email protected]>
Newsgroups org.infradead.lists.linux-arm-kernel,dev.linux.lists.mfd,dev.linux.lists.soc,org.kernel.feeds.b4-sent,org.kernel.vger.linux-clk,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-gpio,org.kernel.vger.linux-kernel,org.kernel.vger.linux-serial
Message-ID <[email protected]>
From: Long Zhao <[email protected]>

Add an 8250 platform driver for Ambarella CV75 instead of claiming
snps,dw-apb-uart compatibility.

Signed-off-by: Long Zhao <[email protected]>
---
 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", &reg_shift);
+	of_property_read_u32(dev->of_node, "reg-io-width", &reg_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 <[email protected]>");
+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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.