[PATCH v2 08/10] serial: ambarella: add Ambarella UART driver
Long Zhao via B4 Relay <[email protected]> Thu, 06 Aug 2026 17:34:16 +0800
| Newsgroups | org.kernel.vger.linux-serial,dev.linux.lists.soc,org.infradead.lists.linux-arm-kernel,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 |
|---|---|
| Message-ID | <20260806-longzhao-upstream-cv75-v2-v2-8-6b09707c5fe9@ambarella.com> |
From: Long Zhao <[email protected]> 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 <[email protected]> --- drivers/tty/serial/Kconfig | 21 + drivers/tty/serial/Makefile | 1 + drivers/tty/serial/ambarella_uart.c | 1001 +++++++++++++++++++++++++++++++++++ 3 files changed, 1023 insertions(+) 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..7356b242f0ef --- /dev/null +++ b/drivers/tty/serial/ambarella_uart.c @@ -0,0 +1,1001 @@ +// 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/iopoll.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 */ + +/* Poll timeout in microseconds (atomic helpers use udelay). */ +#define AMBARELLA_UART_TIMEOUT_US 1000000 + +struct ambarella_uart_port { + struct uart_port port; + struct clk *uart_pll; + unsigned long flags; + u32 mcr; + /* Software copy of UART_IE, updated with the port lock held. */ + u32 ier; + bool console_line_ended; +}; + +static struct ambarella_uart_port ambarella_port[AMBA_UART_MAX_NUM]; + +static struct ambarella_uart_port * +to_ambarella_uart_port(struct uart_port *port) +{ + return container_of(port, struct ambarella_uart_port, port); +} + +static void serial_ambarella_ier_write(struct uart_port *port, u32 ier) +{ + struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port); + + amb_port->ier = ier; + writel_relaxed(ier, port->membase + UART_IE_OFFSET); +} + +static void serial_ambarella_ier_set(struct uart_port *port, u32 set) +{ + struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port); + + amb_port->ier |= set; + writel_relaxed(amb_port->ier, port->membase + UART_IE_OFFSET); +} + +static void serial_ambarella_ier_clear(struct uart_port *port, u32 clear) +{ + struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port); + + amb_port->ier &= ~clear; + writel_relaxed(amb_port->ier, port->membase + UART_IE_OFFSET); +} + +static void serial_ambarella_ier_toggle(struct uart_port *port, u32 mask) +{ + struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port); + + amb_port->ier &= ~mask; + writel_relaxed(amb_port->ier, port->membase + UART_IE_OFFSET); + amb_port->ier |= mask; + writel_relaxed(amb_port->ier, port->membase + UART_IE_OFFSET); +} + +static void __serial_ambarella_stop_tx(struct uart_port *port) +{ + serial_ambarella_ier_clear(port, UART_IE_ETBEI); +} + +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) +{ + serial_ambarella_ier_set(port, UART_IE_EDSSI); +} + +static void __serial_ambarella_disable_ms(struct uart_port *port) +{ + serial_ambarella_ier_clear(port, UART_IE_EDSSI); +} + +static inline void wait_for_tx(struct uart_port *port) +{ + u32 ls; + int ret; + + ret = readl_poll_timeout_atomic(port->membase + UART_LS_OFFSET, ls, + ls & UART_LS_TEMT, 1, + AMBARELLA_UART_TIMEOUT_US); + if (likely(!ret)) + return; + + /* Recover a stuck TX path so console/poll can continue. */ + 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 int wait_for_rx(struct uart_port *port) +{ + u32 ls; + + return readl_relaxed_poll_timeout_atomic(port->membase + UART_LS_OFFSET, + ls, ls & UART_LS_DR, 1, + AMBARELLA_UART_TIMEOUT_US); +} + +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 = to_ambarella_uart_port(port); + + 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. */ + serial_ambarella_ier_write(port, 0); +} + +static inline void serial_ambarella_receive_chars(struct uart_port *port, + u32 tmo) +{ + u32 ch, flag, ls; + bool have_char; + int max_count; + + ls = readl_relaxed(port->membase + UART_LS_OFFSET); + max_count = port->fifosize; + + do { + flag = TTY_NORMAL; + have_char = ls & UART_LS_DR; + if (have_char || (ls & UART_LS_BI) || tmo) + ch = readl_relaxed(port->membase + UART_RB_OFFSET); + if (have_char) { + port->icount.rx++; + tmo = 0; + } + + 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 (have_char) { + if (uart_handle_sysrq_char(port, ch)) + goto ignore_char; + + uart_insert_char(port, ls, UART_LS_OE, ch, flag); + } + +ignore_char: + ls = readl_relaxed(port->membase + UART_LS_OFFSET); + } while ((ls & (UART_LS_DR | UART_LS_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; + u32 ii; + + scoped_guard(uart_port_lock_irqsave, port) { + 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 */ + serial_ambarella_ier_toggle(port, UART_IE_ERETOI); + 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; + } + } + + 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) +{ + /* if transmit buffer is not allocated, just return */ + if (!port->state->port.xmit_buf) + return; + + serial_ambarella_ier_set(port, UART_IE_ETBEI); + 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) +{ + serial_ambarella_ier_clear(port, UART_IE_ERBFI); +} + +static unsigned int serial_ambarella_tx_empty(struct uart_port *port) +{ + unsigned int lsr; + + guard(uart_port_lock_irqsave)(port); + lsr = readl_relaxed(port->membase + UART_LS_OFFSET); + + 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 = to_ambarella_uart_port(port); + u32 mcr, mcr_new = 0; + + 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; + + guard(uart_port_lock_irqsave)(port); + lcr = readl_relaxed(port->membase + UART_LC_OFFSET); + 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); +} + +static void serial_ambarella_hw_deinit(struct ambarella_uart_port *amb_port) +{ + struct uart_port *port = &amb_port->port; + + /* Disable interrupts */ + serial_ambarella_ier_write(port, 0); + + /* 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 = to_ambarella_uart_port(port); + + 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; + } + + serial_ambarella_ier_write(port, DEFAULT_AMBARELLA_UART_IER); + + return 0; +} + +static void serial_ambarella_shutdown(struct uart_port *port) +{ + struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port); + u32 lcr; + + scoped_guard(uart_port_lock_irqsave, port) { + 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); + } + + 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 = to_ambarella_uart_port(port); + unsigned int baud, quot; + u32 lc = 0x0; + + 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); + + scoped_guard(uart_port_lock_irqsave, port) { + 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); + } +} + +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) + return NO_POLL_CHAR; + + if (wait_for_rx(port)) + return NO_POLL_CHAR; + + return readl_relaxed(port->membase + UART_RB_OFFSET); +} +#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_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_putchar(struct uart_port *port, + unsigned char ch) +{ + struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port); + + serial_ambarella_putchar(port, ch); + amb_port->console_line_ended = (ch == '\n'); +} + +static void serial_ambarella_console_device_lock(struct console *co, + unsigned long *flags) +{ + __uart_port_lock_irqsave(&ambarella_port[co->index].port, flags); +} + +static void serial_ambarella_console_device_unlock(struct console *co, + unsigned long flags) +{ + __uart_port_unlock_irqrestore(&ambarella_port[co->index].port, flags); +} + +static void serial_ambarella_console_write_atomic(struct console *co, + struct nbcon_write_context *wctxt) +{ + struct ambarella_uart_port *amb_port = &ambarella_port[co->index]; + struct uart_port *port = &amb_port->port; + + if (port->suspended) + return; + + if (!nbcon_enter_unsafe(wctxt)) + return; + + if (!amb_port->console_line_ended) + uart_console_write(port, "\n", 1, serial_ambarella_console_putchar); + uart_console_write(port, wctxt->outbuf, wctxt->len, + serial_ambarella_console_putchar); + wait_for_tx(port); + + nbcon_exit_unsafe(wctxt); +} + +static void serial_ambarella_console_write_thread(struct console *co, + struct nbcon_write_context *wctxt) +{ + struct ambarella_uart_port *amb_port = &ambarella_port[co->index]; + struct uart_port *port = &amb_port->port; + + if (port->suspended) + return; + + if (!nbcon_enter_unsafe(wctxt)) + return; + + if (nbcon_exit_unsafe(wctxt)) { + unsigned int len = READ_ONCE(wctxt->len); + unsigned int i; + + /* + * Toggle unsafe per byte so a higher-priority context can + * take over. After a failed enter/exit, outbuf/len are no + * longer trusted and printing must stop. + */ + for (i = 0; i < len; i++) { + if (!nbcon_enter_unsafe(wctxt)) + break; + uart_console_write(port, wctxt->outbuf + i, 1, + serial_ambarella_console_putchar); + if (!nbcon_exit_unsafe(wctxt)) + break; + } + } + + while (!nbcon_enter_unsafe(wctxt)) + nbcon_reacquire_nobuf(wctxt); + + wait_for_tx(port); + nbcon_exit_unsafe(wctxt); +} + +static int __init serial_ambarella_console_setup(struct console *co, + char *options) +{ + struct uart_port *port; + struct ambarella_uart_port *amb_port; + int baud = 115200, bits = 8, parity = 'n', flow = 'n'; + + if (co->index < 0 || co->index >= serial_ambarella_reg.nr) + co->index = 0; + + amb_port = &ambarella_port[co->index]; + port = &amb_port->port; + if (!port->membase) { + pr_err("No device available for serial console\n"); + return -ENODEV; + } + + port->ops = &serial_ambarella_pops; + port->line = co->index; + amb_port->console_line_ended = true; + + 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", + .device = uart_console_device, + .setup = serial_ambarella_console_setup, + .write_atomic = serial_ambarella_console_write_atomic, + .write_thread = serial_ambarella_console_write_thread, + .device_lock = serial_ambarella_console_device_lock, + .device_unlock = serial_ambarella_console_device_unlock, + .flags = CON_PRINTBUFFER | CON_ANYTIME | CON_NBCON, + .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_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,cv75-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.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" }, + {}, +}; +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