The Linux Kernel Mailing List
 help / color / mirror / Atom feed
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

  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