From: Long Zhao <longzhao@ambarella.com>
To: Arnd Bergmann <arnd@arndb.de>,
Alexandre Belloni <alexandre.belloni@bootlin.com>,
<linux-arm-kernel@lists.infradead.org>, <soc@lists.linux.dev>
Cc: Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>, <devicetree@vger.kernel.org>,
"Michael Turquette" <mturquette@baylibre.com>,
Stephen Boyd <sboyd@kernel.org>, <linux-clk@vger.kernel.org>,
Linus Walleij <linusw@kernel.org>, <linux-gpio@vger.kernel.org>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Jiri Slaby <jirislaby@kernel.org>, <linux-serial@vger.kernel.org>,
Catalin Marinas <catalin.marinas@arm.com>,
Will Deacon <will@kernel.org>, <linux-kernel@vger.kernel.org>,
Long Zhao <longzhao@ambarella.com>
Subject: [PATCH 08/10] serial: ambarella: add Ambarella UART driver
Date: Thu, 30 Jul 2026 18:43:28 +0800 [thread overview]
Message-ID: <20260730104330.81367-9-longzhao@ambarella.com> (raw)
In-Reply-To: <20260730104330.81367-1-longzhao@ambarella.com>
Add an Ambarella UART driver with console support for early boot
bring-up on CV75. Keep udelay() in wait_for_tx(); it runs under
console/poll paths that may hold the port lock with IRQs disabled.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
drivers/tty/serial/Kconfig | 21 +
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/ambarella_uart.c | 937 ++++++++++++++++++++++++++++
3 files changed, 959 insertions(+)
create mode 100644 drivers/tty/serial/ambarella_uart.c
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index cf7dba473b20..e3f5ec794c72 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -18,6 +18,27 @@ source "drivers/tty/serial/8250/Kconfig"
comment "Non-8250 serial port support"
+config SERIAL_AMBARELLA
+ bool "Ambarella UART support"
+ depends on ARCH_AMBARELLA || COMPILE_TEST
+ select SERIAL_CORE
+ help
+ Say Y here to enable the on-chip UART controller found on Ambarella
+ SoCs. The driver supports interrupt-driven transmit and receive
+ operation through the serial core. It can also provide a system
+ console when SERIAL_AMBARELLA_CONSOLE is enabled.
+
+config SERIAL_AMBARELLA_CONSOLE
+ bool "Console on Ambarella UART"
+ depends on SERIAL_AMBARELLA
+ select SERIAL_CORE_CONSOLE
+ select SERIAL_EARLYCON
+ help
+ Say Y here to use an Ambarella UART as the system console. This
+ enables both the regular serial console and earlycon support for
+ messages emitted before the full UART driver is initialized.
+ Select this option for early boot diagnostics on Ambarella systems.
+
config SERIAL_AMBA_PL010
tristate "ARM AMBA PL010 serial port support"
depends on ARM_AMBA || COMPILE_TEST
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index bba7b21a4a1d..5c951719528b 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -26,6 +26,7 @@ obj-y += 8250/
obj-$(CONFIG_SERIAL_ALTERA_JTAGUART) += altera_jtaguart.o
obj-$(CONFIG_SERIAL_ALTERA_UART) += altera_uart.o
+obj-$(CONFIG_SERIAL_AMBARELLA) += ambarella_uart.o
obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o
obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o
obj-$(CONFIG_SERIAL_GRLIB_GAISLER_APBUART) += apbuart.o
diff --git a/drivers/tty/serial/ambarella_uart.c b/drivers/tty/serial/ambarella_uart.c
new file mode 100644
index 000000000000..e79f27c71986
--- /dev/null
+++ b/drivers/tty/serial/ambarella_uart.c
@@ -0,0 +1,937 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/clk.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/serial_reg.h>
+#include <linux/serial_core.h>
+#include <linux/sysrq.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+
+#define UART_RB_OFFSET 0x00
+#define UART_TH_OFFSET 0x00
+#define UART_DLL_OFFSET 0x00
+#define UART_IE_OFFSET 0x04
+#define UART_DLH_OFFSET 0x04
+#define UART_II_OFFSET 0x08
+#define UART_FC_OFFSET 0x08
+#define UART_LC_OFFSET 0x0c
+#define UART_MC_OFFSET 0x10
+#define UART_LS_OFFSET 0x14
+#define UART_MS_OFFSET 0x18
+#define UART_US_OFFSET 0x7c
+#define UART_SRR_OFFSET 0x88
+
+#define UART_IE_ERETOI 0x40
+#define UART_IE_ETOI 0x20
+#define UART_IE_EDSSI 0x08
+#define UART_IE_ELSI 0x04
+#define UART_IE_ETBEI 0x02
+#define UART_IE_ERBFI 0x01
+
+#define UART_II_MODEM_STATUS_CHANGED 0x00
+#define UART_II_NO_INT_PENDING 0x01
+#define UART_II_THR_EMPTY 0x02
+#define UART_II_RCV_DATA_AVAIL 0x04
+#define UART_II_RCV_STATUS 0x06
+#define UART_II_CHAR_TIMEOUT 0x0c
+#define UART_II_CHAR_TIMEOUT_FIFO_EMPTY 0x0d
+
+#define UART_FC_RX_2_TO_FULL 0xc0
+#define UART_FC_TX_EMPTY 0x00
+#define UART_FC_XMITR 0x04
+#define UART_FC_RCVRR 0x02
+#define UART_FC_FIFOE 0x01
+
+#define UART_LC_DLAB 0x80
+#define UART_LC_BRK 0x40
+#define UART_LC_EVEN_PARITY 0x10
+#define UART_LC_ODD_PARITY 0x00
+#define UART_LC_PEN 0x08
+#define UART_LC_STOP_2BIT 0x04
+#define UART_LC_STOP_1BIT 0x00
+#define UART_LC_CLS_8_BITS 0x03
+#define UART_LC_CLS_7_BITS 0x02
+#define UART_LC_CLS_6_BITS 0x01
+#define UART_LC_CLS_5_BITS 0x00
+
+#define UART_MC_AFCE 0x20
+#define UART_MC_LB 0x10
+#define UART_MC_OUT2 0x08
+#define UART_MC_OUT1 0x04
+#define UART_MC_RTS 0x02
+#define UART_MC_DTR 0x01
+
+#define UART_LS_TEMT 0x40
+#define UART_LS_THRE 0x20
+#define UART_LS_BI 0x10
+#define UART_LS_FE 0x08
+#define UART_LS_PE 0x04
+#define UART_LS_OE 0x02
+#define UART_LS_DR 0x01
+
+#define UART_MS_DCD 0x80
+#define UART_MS_RI 0x40
+#define UART_MS_DSR 0x20
+#define UART_MS_CTS 0x10
+#define UART_MS_DDCD 0x08
+#define UART_MS_DCTS 0x01
+
+#define UART_US_TFNF 0x02
+
+#define UART_FIFO_SIZE 64
+
+#define DEFAULT_AMBARELLA_UART_MCR 0
+#define DEFAULT_AMBARELLA_UART_IER (UART_IE_ELSI | UART_IE_ERBFI | \
+ UART_IE_ETOI)
+
+#define AMBA_UART_MAX_NUM 8
+
+#define AMBA_UART_RESET_FLAG 0 /* bit 0 */
+
+#define AMBARELLA_UART_TIMEOUT (1000000)
+
+struct ambarella_uart_port {
+ struct uart_port port;
+ struct clk *uart_pll;
+ unsigned long flags;
+ u32 mcr;
+};
+
+static struct ambarella_uart_port ambarella_port[AMBA_UART_MAX_NUM];
+
+static void __serial_ambarella_stop_tx(struct uart_port *port)
+{
+ u32 ier;
+
+ ier = readl_relaxed(port->membase + UART_IE_OFFSET);
+ writel_relaxed(ier & ~UART_IE_ETBEI, port->membase + UART_IE_OFFSET);
+}
+
+static u32 __serial_ambarella_read_ms(struct uart_port *port)
+{
+ return readl_relaxed(port->membase + UART_MS_OFFSET);
+}
+
+static void __serial_ambarella_enable_ms(struct uart_port *port)
+{
+ u32 ier = readl_relaxed(port->membase + UART_IE_OFFSET);
+
+ writel_relaxed(ier | UART_IE_EDSSI, port->membase + UART_IE_OFFSET);
+}
+
+static void __serial_ambarella_disable_ms(struct uart_port *port)
+{
+ u32 ier = readl_relaxed(port->membase + UART_IE_OFFSET);
+
+ writel_relaxed(ier & ~UART_IE_EDSSI, port->membase + UART_IE_OFFSET);
+}
+
+static inline void wait_for_tx(struct uart_port *port)
+{
+ unsigned int timeout = AMBARELLA_UART_TIMEOUT;
+
+ while (timeout &&
+ !(readl(port->membase + UART_LS_OFFSET) & UART_LS_TEMT)) {
+ udelay(1);
+ timeout--;
+ }
+
+ if (unlikely(!timeout)) {
+ writel_relaxed(UART_FC_RX_2_TO_FULL | UART_FC_TX_EMPTY |
+ UART_FC_XMITR | UART_FC_RCVRR,
+ port->membase + UART_FC_OFFSET);
+ udelay(100);
+ writel_relaxed(UART_FC_FIFOE | UART_FC_RX_2_TO_FULL |
+ UART_FC_TX_EMPTY | UART_FC_XMITR |
+ UART_FC_RCVRR,
+ port->membase + UART_FC_OFFSET);
+ }
+}
+
+static inline void wait_for_rx(struct uart_port *port)
+{
+ u32 ls;
+
+ ls = readl_relaxed(port->membase + UART_LS_OFFSET);
+ while ((ls & UART_LS_DR) != UART_LS_DR) {
+ cpu_relax();
+ ls = readl_relaxed(port->membase + UART_LS_OFFSET);
+ }
+}
+
+static inline int tx_fifo_is_full(struct uart_port *port)
+{
+ return !(readl_relaxed(port->membase + UART_US_OFFSET) & UART_US_TFNF);
+}
+
+static void serial_ambarella_hw_setup(struct uart_port *port)
+{
+ struct ambarella_uart_port *amb_port;
+
+ amb_port = (struct ambarella_uart_port *)(port->private_data);
+
+ if (!test_and_set_bit(AMBA_UART_RESET_FLAG, &amb_port->flags)) {
+ if (amb_port->uart_pll)
+ port->uartclk = clk_get_rate(amb_port->uart_pll);
+ /* reset the whole UART only once */
+ writel_relaxed(0x01, port->membase + UART_SRR_OFFSET);
+ mdelay(1);
+ writel_relaxed(0x00, port->membase + UART_SRR_OFFSET);
+ }
+
+ writel_relaxed(UART_FC_FIFOE | UART_FC_RX_2_TO_FULL | UART_FC_TX_EMPTY |
+ UART_FC_XMITR | UART_FC_RCVRR, port->membase + UART_FC_OFFSET);
+ /* Keep interrupts disabled until the IRQ handler is registered. */
+ writel_relaxed(0, port->membase + UART_IE_OFFSET);
+}
+
+static inline void serial_ambarella_receive_chars(struct uart_port *port,
+ u32 tmo)
+{
+ u32 ch, flag, ls;
+ int max_count;
+
+ ls = readl_relaxed(port->membase + UART_LS_OFFSET);
+ max_count = port->fifosize;
+
+ do {
+ flag = TTY_NORMAL;
+ if (unlikely(ls & (UART_LS_BI | UART_LS_PE |
+ UART_LS_FE | UART_LS_OE))) {
+ if (ls & UART_LS_BI) {
+ ls &= ~(UART_LS_FE | UART_LS_PE);
+ port->icount.brk++;
+
+ if (uart_handle_break(port))
+ goto ignore_char;
+ }
+ if (ls & UART_LS_FE)
+ port->icount.frame++;
+ if (ls & UART_LS_PE)
+ port->icount.parity++;
+ if (ls & UART_LS_OE)
+ port->icount.overrun++;
+
+ ls &= port->read_status_mask;
+
+ if (ls & UART_LS_BI)
+ flag = TTY_BREAK;
+ else if (ls & UART_LS_FE)
+ flag = TTY_FRAME;
+ else if (ls & UART_LS_PE)
+ flag = TTY_PARITY;
+ else if (ls & UART_LS_OE)
+ flag = TTY_OVERRUN;
+
+ if (ls & UART_LS_OE)
+ pr_debug("%s: OVERFLOW\n", __func__);
+ }
+
+ if (likely(ls & UART_LS_DR)) {
+ ch = readl_relaxed(port->membase + UART_RB_OFFSET);
+ port->icount.rx++;
+ tmo = 0;
+
+ if (uart_handle_sysrq_char(port, ch))
+ goto ignore_char;
+
+ uart_insert_char(port, ls, UART_LS_OE, ch, flag);
+ } else if (tmo) {
+ ch = readl_relaxed(port->membase + UART_RB_OFFSET);
+ }
+
+ignore_char:
+ ls = readl_relaxed(port->membase + UART_LS_OFFSET);
+ } while ((ls & (UART_LS_DR | UART_LSR_BI)) && (max_count-- > 0));
+
+ tty_flip_buffer_push(&port->state->port);
+}
+
+static void serial_ambarella_transmit_chars(struct uart_port *port)
+{
+ struct tty_port *tport = &port->state->port;
+ int count;
+
+ if (port->x_char) {
+ writel_relaxed(port->x_char, port->membase + UART_TH_OFFSET);
+ port->icount.tx++;
+ port->x_char = 0;
+ return;
+ }
+
+ if (uart_tx_stopped(port) || kfifo_is_empty(&tport->xmit_fifo)) {
+ __serial_ambarella_stop_tx(port);
+ return;
+ }
+
+ count = port->fifosize;
+ while (count-- > 0) {
+ unsigned char c;
+
+ if (tx_fifo_is_full(port))
+ break;
+
+ if (!kfifo_peek(&tport->xmit_fifo, &c))
+ break;
+
+ writel_relaxed(c, port->membase + UART_TH_OFFSET);
+ kfifo_skip(&tport->xmit_fifo);
+ port->icount.tx++;
+ if (kfifo_is_empty(&tport->xmit_fifo))
+ break;
+ }
+
+ if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
+ uart_write_wakeup(port);
+ if (kfifo_is_empty(&tport->xmit_fifo))
+ __serial_ambarella_stop_tx(port);
+}
+
+static inline void serial_ambarella_check_modem_status(struct uart_port *port)
+{
+ u32 ms;
+
+ ms = __serial_ambarella_read_ms(port);
+
+ if (ms & UART_MS_RI)
+ port->icount.rng++;
+ if (ms & UART_MS_DSR)
+ port->icount.dsr++;
+ if (ms & UART_MS_DCTS)
+ uart_handle_cts_change(port, (ms & UART_MS_CTS));
+ if (ms & UART_MS_DDCD)
+ uart_handle_dcd_change(port, (ms & UART_MS_DCD));
+
+ wake_up_interruptible(&port->state->port.delta_msr_wait);
+}
+
+static irqreturn_t serial_ambarella_irq(int irq, void *dev_id)
+{
+ struct uart_port *port = dev_id;
+ struct ambarella_uart_port *amb_port;
+ unsigned long flags;
+ u32 ii, ier;
+
+ amb_port = (struct ambarella_uart_port *)(port->private_data);
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ ii = readl_relaxed(port->membase + UART_II_OFFSET);
+ switch (ii & 0x0F) {
+ case UART_II_MODEM_STATUS_CHANGED:
+ serial_ambarella_check_modem_status(port);
+ break;
+ case UART_II_THR_EMPTY:
+ serial_ambarella_transmit_chars(port);
+ break;
+ case UART_II_RCV_STATUS:
+ case UART_II_RCV_DATA_AVAIL:
+ serial_ambarella_receive_chars(port, 0);
+ break;
+ case UART_II_CHAR_TIMEOUT_FIFO_EMPTY:
+ /* Clear ERETOI to dismiss timeout-with-empty-FIFO IRQ */
+ ier = readl_relaxed(port->membase + UART_IE_OFFSET);
+ writel_relaxed(ier & ~UART_IE_ERETOI,
+ amb_port->port.membase + UART_IE_OFFSET);
+ writel_relaxed(ier | UART_IE_ERETOI,
+ amb_port->port.membase + UART_IE_OFFSET);
+ fallthrough;
+ case UART_II_CHAR_TIMEOUT:
+ serial_ambarella_receive_chars(port, 1);
+ break;
+ case UART_II_NO_INT_PENDING:
+ break;
+ default:
+ pr_debug("%s: 0x%x\n", __func__, ii);
+ break;
+ }
+
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static void serial_ambarella_enable_ms(struct uart_port *port)
+{
+ __serial_ambarella_enable_ms(port);
+}
+
+static void serial_ambarella_start_tx(struct uart_port *port)
+{
+ u32 ier;
+
+ /* if transmit buffer is not allocated, just return */
+ if (!port->state->port.xmit_buf)
+ return;
+
+ ier = readl_relaxed(port->membase + UART_IE_OFFSET);
+ writel_relaxed(ier | UART_IE_ETBEI, port->membase + UART_IE_OFFSET);
+
+ serial_ambarella_transmit_chars(port);
+}
+
+static void serial_ambarella_stop_tx(struct uart_port *port)
+{
+ __serial_ambarella_stop_tx(port);
+}
+
+static void serial_ambarella_stop_rx(struct uart_port *port)
+{
+ u32 ier;
+
+ ier = readl_relaxed(port->membase + UART_IE_OFFSET);
+ writel_relaxed(ier & ~UART_IE_ERBFI, port->membase + UART_IE_OFFSET);
+}
+
+static unsigned int serial_ambarella_tx_empty(struct uart_port *port)
+{
+ unsigned int lsr;
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+ lsr = readl_relaxed(port->membase + UART_LS_OFFSET);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ return ((lsr & (UART_LS_TEMT | UART_LS_THRE)) ==
+ (UART_LS_TEMT | UART_LS_THRE)) ? TIOCSER_TEMT : 0;
+}
+
+static unsigned int serial_ambarella_get_mctrl(struct uart_port *port)
+{
+ u32 ms, mctrl = 0;
+
+ ms = __serial_ambarella_read_ms(port);
+
+ if (ms & UART_MS_CTS)
+ mctrl |= TIOCM_CTS;
+ if (ms & UART_MS_DSR)
+ mctrl |= TIOCM_DSR;
+ if (ms & UART_MS_RI)
+ mctrl |= TIOCM_RI;
+ if (ms & UART_MS_DCD)
+ mctrl |= TIOCM_CD;
+
+ return mctrl;
+}
+
+static void serial_ambarella_set_mctrl(struct uart_port *port,
+ unsigned int mctrl)
+{
+ struct ambarella_uart_port *amb_port;
+ u32 mcr, mcr_new = 0;
+
+ amb_port = (struct ambarella_uart_port *)(port->private_data);
+
+ mcr = readl_relaxed(port->membase + UART_MC_OFFSET);
+
+ if (mctrl & TIOCM_DTR)
+ mcr_new |= UART_MC_DTR;
+ if (mctrl & TIOCM_RTS)
+ mcr_new |= UART_MC_RTS;
+ if (mctrl & TIOCM_OUT1)
+ mcr_new |= UART_MC_OUT1;
+ if (mctrl & TIOCM_OUT2)
+ mcr_new |= UART_MC_OUT2;
+ if (mctrl & TIOCM_LOOP)
+ mcr_new |= UART_MC_LB;
+
+ mcr_new |= amb_port->mcr;
+ if (mcr_new != mcr) {
+ if ((mcr & UART_MC_AFCE) == UART_MC_AFCE) {
+ mcr &= ~UART_MC_AFCE;
+ writel_relaxed(mcr, port->membase + UART_MC_OFFSET);
+ }
+ writel_relaxed(mcr_new, port->membase + UART_MC_OFFSET);
+ }
+}
+
+static void serial_ambarella_break_ctl(struct uart_port *port, int break_state)
+{
+ u32 lcr = readl_relaxed(port->membase + UART_LC_OFFSET);
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+ if (break_state != 0)
+ writel_relaxed(lcr | UART_LC_BRK, port->membase + UART_LC_OFFSET);
+ else
+ writel_relaxed(lcr & ~UART_LC_BRK, port->membase + UART_LC_OFFSET);
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static void serial_ambarella_hw_deinit(struct ambarella_uart_port *amb_port)
+{
+ struct uart_port *port = &amb_port->port;
+
+ /* Disable interrupts */
+ writel_relaxed(0, port->membase + UART_IE_OFFSET);
+
+ /* Reset the Rx and Tx FIFOs */
+ writel_relaxed(UART_FCR_CLEAR_XMIT | UART_FCR_CLEAR_RCVR,
+ port->membase + UART_SRR_OFFSET);
+}
+
+static int serial_ambarella_startup(struct uart_port *port)
+{
+ int rval;
+ struct ambarella_uart_port *amb_port;
+
+ amb_port = (struct ambarella_uart_port *)(port->private_data);
+
+ serial_ambarella_hw_setup(port);
+
+ rval = request_irq(port->irq, serial_ambarella_irq, IRQF_TRIGGER_HIGH,
+ dev_name(amb_port->port.dev), &amb_port->port);
+ if (rval < 0) {
+ dev_err(amb_port->port.dev,
+ "Failed to register ISR for IRQ %d\n", port->irq);
+ serial_ambarella_hw_deinit(amb_port);
+ return rval;
+ }
+
+ writel_relaxed(DEFAULT_AMBARELLA_UART_IER,
+ port->membase + UART_IE_OFFSET);
+
+ return 0;
+}
+
+static void serial_ambarella_shutdown(struct uart_port *port)
+{
+ struct ambarella_uart_port *amb_port;
+ u32 lcr;
+ unsigned long flags;
+
+ amb_port = (struct ambarella_uart_port *)(port->private_data);
+
+ spin_lock_irqsave(&port->lock, flags);
+ serial_ambarella_hw_deinit(amb_port);
+ lcr = readl_relaxed(port->membase + UART_LC_OFFSET);
+ writel_relaxed(lcr & ~UART_LC_BRK, port->membase + UART_LC_OFFSET);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ free_irq(amb_port->port.irq, &amb_port->port);
+}
+
+static void serial_ambarella_set_termios(struct uart_port *port,
+ struct ktermios *termios,
+ const struct ktermios *old)
+{
+ struct ambarella_uart_port *amb_port;
+ unsigned int baud, quot;
+ unsigned long flags;
+ u32 lc = 0x0;
+
+ amb_port = (struct ambarella_uart_port *)(port->private_data);
+
+ port->uartclk = clk_get_rate(amb_port->uart_pll);
+ switch (termios->c_cflag & CSIZE) {
+ case CS5:
+ lc |= UART_LC_CLS_5_BITS;
+ break;
+ case CS6:
+ lc |= UART_LC_CLS_6_BITS;
+ break;
+ case CS7:
+ lc |= UART_LC_CLS_7_BITS;
+ break;
+ case CS8:
+ default:
+ lc |= UART_LC_CLS_8_BITS;
+ break;
+ }
+
+ if (termios->c_cflag & CSTOPB)
+ lc |= UART_LC_STOP_2BIT;
+ else
+ lc |= UART_LC_STOP_1BIT;
+
+ if (termios->c_cflag & PARENB) {
+ if (termios->c_cflag & PARODD)
+ lc |= (UART_LC_PEN | UART_LC_ODD_PARITY);
+ else
+ lc |= (UART_LC_PEN | UART_LC_EVEN_PARITY);
+ }
+
+ baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16);
+ quot = uart_get_divisor(port, baud);
+
+ spin_lock_irqsave(&port->lock, flags);
+ uart_update_timeout(port, termios->c_cflag, baud);
+
+ port->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
+ if (termios->c_iflag & INPCK)
+ port->read_status_mask |= UART_LSR_FE | UART_LSR_PE;
+ if (termios->c_iflag & (BRKINT | PARMRK))
+ port->read_status_mask |= UART_LSR_BI;
+
+ port->ignore_status_mask = 0;
+ if (termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
+ if (termios->c_iflag & IGNBRK) {
+ port->ignore_status_mask |= UART_LSR_BI;
+ if (termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= UART_LSR_OE;
+ }
+ if ((termios->c_cflag & CREAD) == 0)
+ port->ignore_status_mask |= UART_LSR_DR;
+
+ if ((termios->c_cflag & CRTSCTS) == 0) {
+ amb_port->mcr &= ~UART_MC_AFCE;
+ port->status &= ~UPSTAT_AUTOCTS;
+ } else {
+ amb_port->mcr |= UART_MC_AFCE;
+ port->status |= UPSTAT_AUTOCTS;
+ }
+
+ writel_relaxed(UART_LC_DLAB, port->membase + UART_LC_OFFSET);
+ writel_relaxed(quot & 0xff, port->membase + UART_DLL_OFFSET);
+ writel_relaxed((quot >> 8) & 0xff, port->membase + UART_DLH_OFFSET);
+ writel_relaxed(lc, port->membase + UART_LC_OFFSET);
+ if (UART_ENABLE_MS(port, termios->c_cflag))
+ __serial_ambarella_enable_ms(port);
+ else
+ __serial_ambarella_disable_ms(port);
+ serial_ambarella_set_mctrl(port, port->mctrl);
+
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static void serial_ambarella_pm(struct uart_port *port,
+ unsigned int state, unsigned int oldstate)
+{
+}
+
+static void serial_ambarella_release_port(struct uart_port *port)
+{
+}
+
+static int serial_ambarella_request_port(struct uart_port *port)
+{
+ return 0;
+}
+
+static void serial_ambarella_config_port(struct uart_port *port, int flags)
+{
+}
+
+static int serial_ambarella_verify_port(struct uart_port *port,
+ struct serial_struct *ser)
+{
+ int rval = 0;
+
+ if (ser->type != PORT_UNKNOWN && ser->type != PORT_UART00)
+ rval = -EINVAL;
+ if (port->irq != ser->irq)
+ rval = -EINVAL;
+ if (ser->io_type != SERIAL_IO_MEM)
+ rval = -EINVAL;
+
+ return rval;
+}
+
+static const char *serial_ambarella_type(struct uart_port *port)
+{
+ return "ambuart";
+}
+
+#ifdef CONFIG_CONSOLE_POLL
+static void serial_ambarella_poll_put_char(struct uart_port *port,
+ unsigned char chr)
+{
+ if (!port->suspended) {
+ wait_for_tx(port);
+ writel_relaxed(chr, port->membase + UART_TH_OFFSET);
+ }
+}
+
+static int serial_ambarella_poll_get_char(struct uart_port *port)
+{
+ if (!port->suspended) {
+ wait_for_rx(port);
+ return readl_relaxed(port->membase + UART_RB_OFFSET);
+ }
+ return 0;
+}
+#endif
+
+static const struct uart_ops serial_ambarella_pops = {
+ .tx_empty = serial_ambarella_tx_empty,
+ .set_mctrl = serial_ambarella_set_mctrl,
+ .get_mctrl = serial_ambarella_get_mctrl,
+ .stop_tx = serial_ambarella_stop_tx,
+ .start_tx = serial_ambarella_start_tx,
+ .stop_rx = serial_ambarella_stop_rx,
+ .enable_ms = serial_ambarella_enable_ms,
+ .break_ctl = serial_ambarella_break_ctl,
+ .startup = serial_ambarella_startup,
+ .shutdown = serial_ambarella_shutdown,
+ .set_termios = serial_ambarella_set_termios,
+ .pm = serial_ambarella_pm,
+ .type = serial_ambarella_type,
+ .release_port = serial_ambarella_release_port,
+ .request_port = serial_ambarella_request_port,
+ .config_port = serial_ambarella_config_port,
+ .verify_port = serial_ambarella_verify_port,
+#ifdef CONFIG_CONSOLE_POLL
+ .poll_put_char = serial_ambarella_poll_put_char,
+ .poll_get_char = serial_ambarella_poll_get_char,
+#endif
+};
+
+#if defined(CONFIG_SERIAL_AMBARELLA_CONSOLE)
+
+static struct uart_driver serial_ambarella_reg;
+
+static void serial_ambarella_console_putchar(struct uart_port *port,
+ unsigned char ch)
+{
+ wait_for_tx(port);
+ writel_relaxed(ch, port->membase + UART_TH_OFFSET);
+}
+
+static void serial_ambarella_console_write(struct console *co,
+ const char *s, unsigned int count)
+{
+ struct uart_port *port;
+ int locked = 1;
+ unsigned long flags;
+
+ port = &ambarella_port[co->index].port;
+
+ if (!port->suspended) {
+ if (port->sysrq)
+ locked = 0;
+ else if (oops_in_progress)
+ locked = uart_port_trylock_irqsave(port, &flags);
+ else
+ uart_port_lock_irqsave(port, &flags);
+
+ uart_console_write(port, s, count,
+ serial_ambarella_console_putchar);
+ wait_for_tx(port);
+
+ if (locked)
+ uart_port_unlock_irqrestore(port, flags);
+ }
+}
+
+static int __init serial_ambarella_console_setup(struct console *co,
+ char *options)
+{
+ struct uart_port *port;
+ int baud = 115200, bits = 8, parity = 'n', flow = 'n';
+
+ if (co->index < 0 || co->index >= serial_ambarella_reg.nr)
+ co->index = 0;
+
+ port = &ambarella_port[co->index].port;
+ if (!port->membase) {
+ pr_err("No device available for serial console\n");
+ return -ENODEV;
+ }
+
+ port->ops = &serial_ambarella_pops;
+ port->private_data = &ambarella_port[co->index];
+ port->line = co->index;
+
+ serial_ambarella_hw_setup(port);
+
+ if (options)
+ uart_parse_options(options, &baud, &parity, &bits, &flow);
+
+ return uart_set_options(port, co, baud, parity, bits, flow);
+}
+
+static struct console serial_ambarella_console = {
+ .name = "ttyAB",
+ .write = serial_ambarella_console_write,
+ .device = uart_console_device,
+ .setup = serial_ambarella_console_setup,
+ .flags = CON_PRINTBUFFER | CON_ANYTIME,
+ .index = -1,
+ .data = &serial_ambarella_reg,
+};
+
+static void serial_ambarella_console_early_write(struct console *con,
+ const char *s,
+ unsigned int count)
+{
+ struct earlycon_device *dev = con->data;
+
+ uart_console_write(&dev->port, s, count, serial_ambarella_console_putchar);
+}
+
+static int __init serial_ambarella_console_early_setup(struct earlycon_device *dev,
+ const char *opt)
+{
+ if (!dev->port.membase)
+ return -ENODEV;
+
+ dev->con->write = serial_ambarella_console_early_write;
+
+ return 0;
+}
+
+OF_EARLYCON_DECLARE(ambarella_uart, "ambarella,uart", serial_ambarella_console_early_setup);
+
+#define AMBARELLA_CONSOLE (&serial_ambarella_console)
+#else
+#define AMBARELLA_CONSOLE NULL
+#endif
+
+static struct uart_driver serial_ambarella_reg = {
+ .owner = THIS_MODULE,
+ .driver_name = "ambarella-uart",
+ .dev_name = "ttyAB",
+ .major = 0,
+ .minor = 0,
+ .nr = AMBA_UART_MAX_NUM,
+ .cons = AMBARELLA_CONSOLE,
+};
+
+static int serial_ambarella_probe(struct platform_device *pdev)
+{
+ struct ambarella_uart_port *amb_port;
+ struct resource *mem;
+ struct pinctrl *pinctrl;
+ int irq, id, rval;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "no mem resource!\n");
+ return -ENODEV;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "no irq resource!\n");
+ return -ENODEV;
+ }
+
+ id = of_alias_get_id(pdev->dev.of_node, "serial");
+ if (id < 0 || id >= serial_ambarella_reg.nr) {
+ dev_err(&pdev->dev, "Invalid uart ID %d!\n", id);
+ return -ENXIO;
+ }
+
+ pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
+ if (IS_ERR(pinctrl)) {
+ dev_err(&pdev->dev, "Failed to request pinctrl\n");
+ return PTR_ERR(pinctrl);
+ }
+
+ amb_port = &ambarella_port[id];
+
+ amb_port->uart_pll = devm_clk_get_enabled(&pdev->dev, NULL);
+ if (IS_ERR(amb_port->uart_pll)) {
+ dev_err(&pdev->dev, "Get uart clk failed!\n");
+ return PTR_ERR(amb_port->uart_pll);
+ }
+
+ amb_port->mcr = DEFAULT_AMBARELLA_UART_MCR;
+
+ amb_port->port.dev = &pdev->dev;
+ amb_port->port.type = PORT_UART00;
+ amb_port->port.iotype = UPIO_MEM;
+ amb_port->port.fifosize = UART_FIFO_SIZE;
+ amb_port->port.uartclk = clk_get_rate(amb_port->uart_pll);
+ amb_port->port.ops = &serial_ambarella_pops;
+ amb_port->port.private_data = amb_port;
+ amb_port->port.irq = irq;
+ amb_port->port.line = id;
+ amb_port->port.mapbase = mem->start;
+ amb_port->port.membase = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(amb_port->port.membase))
+ return PTR_ERR(amb_port->port.membase);
+
+ rval = uart_add_one_port(&serial_ambarella_reg, &amb_port->port);
+ if (rval < 0)
+ dev_err(&pdev->dev, "failed to add port: %d, %d!\n", id, rval);
+
+ platform_set_drvdata(pdev, amb_port);
+
+ return rval;
+}
+
+static void serial_ambarella_remove(struct platform_device *pdev)
+{
+ struct ambarella_uart_port *amb_port;
+
+ amb_port = platform_get_drvdata(pdev);
+ uart_remove_one_port(&serial_ambarella_reg, &amb_port->port);
+}
+
+static int serial_ambarella_suspend(struct device *dev)
+{
+ struct ambarella_uart_port *amb_port = dev_get_drvdata(dev);
+
+ return uart_suspend_port(&serial_ambarella_reg, &amb_port->port);
+}
+
+static int serial_ambarella_resume(struct device *dev)
+{
+ struct ambarella_uart_port *amb_port = dev_get_drvdata(dev);
+
+ clear_bit(AMBA_UART_RESET_FLAG, &amb_port->flags);
+ serial_ambarella_hw_setup(&amb_port->port);
+
+ return uart_resume_port(&serial_ambarella_reg, &amb_port->port);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(serial_ambarella_pm_ops,
+ serial_ambarella_suspend,
+ serial_ambarella_resume);
+
+static const struct of_device_id ambarella_serial_of_match[] = {
+ { .compatible = "ambarella,cv75-uart" },
+ { .compatible = "ambarella,uart" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ambarella_serial_of_match);
+
+static struct platform_driver serial_ambarella_driver = {
+ .probe = serial_ambarella_probe,
+ .remove = serial_ambarella_remove,
+ .driver = {
+ .name = "ambarella-uart",
+ .of_match_table = ambarella_serial_of_match,
+ .pm = pm_sleep_ptr(&serial_ambarella_pm_ops),
+ },
+};
+
+static int __init serial_ambarella_init(void)
+{
+ int rval;
+
+ rval = uart_register_driver(&serial_ambarella_reg);
+ if (rval < 0)
+ return rval;
+
+ rval = platform_driver_register(&serial_ambarella_driver);
+ if (rval < 0) {
+ uart_unregister_driver(&serial_ambarella_reg);
+ return rval;
+ }
+
+ return 0;
+}
+
+static void __exit serial_ambarella_exit(void)
+{
+ platform_driver_unregister(&serial_ambarella_driver);
+ uart_unregister_driver(&serial_ambarella_reg);
+}
+
+module_init(serial_ambarella_init);
+module_exit(serial_ambarella_exit);
+
+MODULE_DESCRIPTION("Ambarella UART driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ambarella-uart");
--
2.34.1
NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
next prev parent reply other threads:[~2026-07-30 11:06 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-30 10:43 [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao
2026-07-30 10:43 ` [PATCH 01/10] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao
2026-07-30 10:43 ` [PATCH 02/10] dt-bindings: soc: add Ambarella secure scratchpad Long Zhao
2026-07-30 10:43 ` [PATCH 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller Long Zhao
2026-07-30 12:42 ` Rob Herring (Arm)
2026-07-30 14:11 ` Rob Herring
2026-07-30 10:43 ` [PATCH 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao
2026-07-30 12:42 ` Rob Herring (Arm)
2026-07-30 14:21 ` Rob Herring
2026-07-30 10:43 ` [PATCH 05/10] dt-bindings: serial: add Ambarella UART Long Zhao
2026-07-30 10:49 ` Krzysztof Kozlowski
2026-07-30 10:43 ` [PATCH 06/10] clk: ambarella: add CV75 CCU driver Long Zhao
2026-07-30 10:43 ` [PATCH 07/10] pinctrl: ambarella: add Ambarella pin controller Long Zhao
2026-07-30 10:43 ` Long Zhao [this message]
2026-07-30 10:54 ` [PATCH 08/10] serial: ambarella: add Ambarella UART driver Greg Kroah-Hartman
2026-07-30 10:43 ` [PATCH 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao
2026-07-30 12:18 ` Marc Zyngier
2026-07-30 10:43 ` [PATCH 10/10] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao
2026-07-30 10:48 ` [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Krzysztof Kozlowski
2026-07-30 11:05 ` [EXT] " Long Zhao
2026-07-30 11:56 ` Alexandre Belloni
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260730104330.81367-9-longzhao@ambarella.com \
--to=longzhao@ambarella.com \
--cc=alexandre.belloni@bootlin.com \
--cc=arnd@arndb.de \
--cc=catalin.marinas@arm.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=gregkh@linuxfoundation.org \
--cc=jirislaby@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linusw@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-clk@vger.kernel.org \
--cc=linux-gpio@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-serial@vger.kernel.org \
--cc=mturquette@baylibre.com \
--cc=robh@kernel.org \
--cc=sboyd@kernel.org \
--cc=soc@lists.linux.dev \
--cc=will@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox