Monday, May 27, 2024

A note on linux UARTs

Which /dev/tty# binds to which UART

e.g. from imx8qxp

Suppose we have enabled the lpuart2 in our device tree.

&lpuart2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lpuart2>;
status = "okay";
};
&iomuxc {
pinctrl-names = "default";
pinctrl-0 = ..;
pinctrl_lpuart2: lpuart2grp {
fsl,pins = <
IMX8QXP_UART2_RX_ADMA_UART2_RX                 0x06000020
IMX8QXP_UART2_TX_ADMA_UART2_TX                 0x06000020
>;
};
};

Compatible driver for lpuart is drivers/tty/serial/fsl_lpuart.c

lpuart2: serial@5a080000 {
    compatible = "fsl,imx8qxp-lpuart", "fsl,imx7ulp-lpuart";
    reg = <0x5a080000 0x1000>;
    :
};

In drivers/tty/serial/fsl_lpuart.c we see

static const struct of_device_id lpuart_dt_ids[] = {
:
{ .compatible = "fsl,imx8qxp-lpuart", .data = &imx8qxp_data, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, lpuart_dt_ids);


Here's how it gets the name we see in /dev/tty#

In drivers/tty/serial/fsl_lpuart.c we see

#define DEV_NAME "ttyLP"

static struct uart_driver lpuart_reg = {
.owner          = THIS_MODULE,
.driver_name = DRIVER_NAME,
.dev_name       = DEV_NAME,
.nr             = ARRAY_SIZE(lpuart_ports),
.cons           = LPUART_CONSOLE,
};

static int lpuart_probe(struct platform_device *pdev)
{
        :
ret = uart_add_one_port(&lpuart_reg, &sport->port);
        :
}


From serial_core.c

int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
        :
uport->cons = drv->cons;
uport->minor = drv->tty_driver->minor_start + uport->line;
uport->name = kasprintf(GFP_KERNEL, "%s%d", drv->dev_name,
drv->tty_driver->name_base + uport->line);
        :
}


With all this in place, we'll see it in the device

$ ls /sys/class/tty/ -l
lrwxrwxrwx 1 root root 0 Mar 24 10:25 ttyLP2 -> ../../devices/platform/bus@5a000000/5a080000.serial/tty/ttyLP2