From: Jiri Slaby <jirislaby@kernel.org>
To: longzhao@ambarella.com, Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Michael Turquette <mturquette@baylibre.com>,
Stephen Boyd <sboyd@kernel.org>,
Linus Walleij <linusw@kernel.org>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Catalin Marinas <catalin.marinas@arm.com>,
Will Deacon <will@kernel.org>, Arnd Bergmann <arnd@arndb.de>,
Alexandre Belloni <alexandre.belloni@bootlin.com>
Cc: devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-clk@vger.kernel.org,
linux-gpio@vger.kernel.org, linux-serial@vger.kernel.org,
soc@lists.linux.dev
Subject: Re: [PATCH v2 08/10] serial: ambarella: add Ambarella UART driver
Date: Fri, 7 Aug 2026 08:18:14 +0200 [thread overview]
Message-ID: <13ad992e-c9e1-4fed-af45-32907def6516@kernel.org> (raw)
In-Reply-To: <20260806-longzhao-upstream-cv75-v2-v2-8-6b09707c5fe9@ambarella.com>
Hi,
On 06. 08. 26, 11:34, Long Zhao via B4 Relay wrote:
> 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.
...
> 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
I would put it after AMBA_*. That should be also the ascii order, right?
> 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
For me, this would be more understadable:
#define UART_IE 0x4
# define UART_IE_ERBFI BIT(0)
...
# define UART_IE_ETOI BIT(6)
# define UART_IE_ERETOI BIT(7)
and so on.
That is:
* define the bits near the offset
* no _OFFSET suffix
* use BIT()
...
> +#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 */
Perhaps convert this one to an enum?
> +/* Poll timeout in microseconds (atomic helpers use udelay). */
> +#define AMBARELLA_UART_TIMEOUT_US 1000000
USEC_PER_SEC
> +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))
How did you measure this "likely" matters?
> + 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 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);
Could you document also this bit in SRR (by a macro)?
> + mdelay(1);
1 ms of spinning? That's very bad. Why this cannot be a sleep instead?
> + 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 void serial_ambarella_transmit_chars(struct uart_port *port)
Any reason not to use uart_port_tx_limited()?
> +{
> + 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) {
This needs not to be scoped. Just guard().
> + 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);
Hmm, the last param looks like a bool. Why do you have tmo declared as u32?
> + break;
> + case UART_II_NO_INT_PENDING:
> + break;
> + default:
> + pr_debug("%s: 0x%x\n", __func__, ii);
Are you sure you want to dump the unknown irq for every interrupt (on
DEBUG)?
> + break;
> + }
> + }
> +
> + return IRQ_HANDLED;
> +}
...> +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);
u32 lsr = ...
No need for the previous declaration.
> +
> + return ((lsr & (UART_LS_TEMT | UART_LS_THRE)) ==
> + (UART_LS_TEMT | UART_LS_THRE)) ? TIOCSER_TEMT : 0;
> +}
...
> +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) {
No need for scoped.
> + 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)
> +{
> +}
No need for empty definitions. The hooks are optional. We should
document this, likely.
> +static int serial_ambarella_verify_port(struct uart_port *port,
> + struct serial_struct *ser)
> +{
> + int rval = 0;
You can return immediately, without the need for this variable, right?
> +
> + 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 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);
You set this even on failure. Does it matter? I don't know.
> +
> + return rval;
> +}
> +
> +static void serial_ambarella_remove(struct platform_device *pdev)
> +{
> + struct ambarella_uart_port *amb_port;
> +
> + amb_port = platform_get_drvdata(pdev);
This can be amended to the declaration.
> + uart_remove_one_port(&serial_ambarella_reg, &amb_port->port);
> +}
> +
thanks,
--
js
suse labs
next prev parent reply other threads:[~2026-08-07 6:18 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-06 9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
2026-08-06 9:34 ` [PATCH v2 01/10] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
2026-08-07 6:00 ` Krzysztof Kozlowski
2026-08-06 9:34 ` [PATCH v2 02/10] dt-bindings: soc: add Ambarella secure scratchpad Long Zhao via B4 Relay
2026-08-07 6:01 ` Krzysztof Kozlowski
2026-08-07 8:29 ` Long Zhao
2026-08-06 9:34 ` [PATCH v2 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller Long Zhao via B4 Relay
2026-08-06 10:56 ` Rob Herring (Arm)
2026-08-07 6:02 ` Krzysztof Kozlowski
2026-08-06 9:34 ` [PATCH v2 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
2026-08-07 6:06 ` Krzysztof Kozlowski
2026-08-06 9:34 ` [PATCH v2 05/10] dt-bindings: serial: add Ambarella UART Long Zhao via B4 Relay
2026-08-06 9:34 ` [PATCH v2 06/10] clk: ambarella: add CV75 CCU driver Long Zhao via B4 Relay
2026-08-06 9:34 ` [PATCH v2 07/10] pinctrl: ambarella: add Ambarella pin controller Long Zhao via B4 Relay
2026-08-07 17:45 ` Linus Walleij
2026-08-06 9:34 ` [PATCH v2 08/10] serial: ambarella: add Ambarella UART driver Long Zhao via B4 Relay
2026-08-07 6:18 ` Jiri Slaby [this message]
2026-08-07 18:37 ` Linus Walleij
2026-08-06 9:34 ` [PATCH v2 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao via B4 Relay
2026-08-07 6:11 ` Krzysztof Kozlowski
2026-08-06 9:34 ` [PATCH v2 10/10] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao via B4 Relay
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=13ad992e-c9e1-4fed-af45-32907def6516@kernel.org \
--to=jirislaby@kernel.org \
--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=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=longzhao@ambarella.com \
--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