Re: [PATCH v2 08/10] serial: ambarella: add Ambarella UART driver
Jiri Slaby <[email protected]> Fri, 7 Aug 2026 08:18:14 +0200
| Newsgroups | org.kernel.vger.linux-serial,dev.linux.lists.soc,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-clk,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-gpio,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
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